diff --git a/build.sh b/build.sh index e2bb12556828240558c4d2f84e76ce68c945ca58..a71fe6a0e048c384e80834d7399208a07d367f2c 100755 --- a/build.sh +++ b/build.sh @@ -31,15 +31,18 @@ popd # # gamemoderun -if [ -x "$(command -v gamemoderun)" ]; then - EXE="gamemoderun $EXE" -fi +#if [ -x "$(command -v gamemoderun)" ]; then +# EXE="gamemoderun $EXE" +#fi # mangohud -if [ -x "$(command -v mangohud)" ]; then - EXE="mangohud --dlsym $EXE" -fi +#if [ -x "$(command -v mangohud)" ]; then +# EXE="mangohud --dlsym $EXE" +#fi + +EXE="gdb $EXE" pushd demo -$EXE ../build/demo/FgglDemo > /tmp/fggl.log 2>&1 & +#$EXE ../build/demo/FgglDemo > /tmp/fggl.log 2>&1 +$EXE popd diff --git a/demo/main.cpp b/demo/main.cpp index 07cb316a8d693122d1903cd0676ac795d802b9c6..5a0cf4a31e815fadcd1160684490c0009175da12 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,7 +1,4 @@ #include <filesystem> -#include <glm/ext/matrix_transform.hpp> -#include <glm/geometric.hpp> -#include <glm/trigonometric.hpp> #include <iostream> #include <fggl/gfx/window.hpp> @@ -9,11 +6,19 @@ #include <fggl/gfx/renderer.hpp> #include <fggl/gfx/shader.hpp> #include <fggl/gfx/camera.hpp> + +#include <fggl/ecs2/ecs.cpp> +#include <fggl/gfx/ecs.hpp> +#include <fggl/math/ecs.hpp> + #include <fggl/data/procedural.hpp> #include <fggl/ecs/ecs.hpp> #include <fggl/debug/debug.h> #include <fggl/data/storage.hpp> +#include <glm/glm.hpp> +#include <glm/gtc/type_ptr.hpp> + #include <imgui.h> constexpr bool showNormals = false; @@ -50,7 +55,7 @@ enum camera_type { cam_free, cam_arcball }; camera_type cam_mode = cam_free; //TODO proper input system -void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { +void process_camera(fggl::gfx::Window& window, fggl::ecs2::World& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { if ( glfwGetKey(window.handle(), GLFW_KEY_F2) == GLFW_PRESS ) { cam_mode = cam_free; @@ -59,7 +64,8 @@ void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::I cam_mode = cam_arcball; } - auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); + /* + auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); const glm::vec3 dir = ( camTransform->origin() - camComp->target ); @@ -72,16 +78,28 @@ void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::I motion -= (forward * delta); camTransform->origin( camTransform->origin() + motion ); + */ } -void process_arcball(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { - // see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/ - auto camTransform = ecs.getComponent<fggl::math::Transform>(cam); - auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam); +struct CameraHacks { + fggl::gfx::Window& window; + fggl::gfx::Input& input; +}; - glm::vec4 position(camTransform->origin(), 1.0f); - glm::vec4 pivot(camComp->target, 1.0f); - glm::mat4 view = glm::lookAt( camTransform->origin(), camComp->target, camTransform->up() ); +void CameraArcball(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) { + CameraHacks* hacks = (CameraHacks*)( it.ctx() ); + const fggl::gfx::Window& window = hacks->window; + const fggl::gfx::Input& input = hacks->input; + + auto& camTransform = t[0]; + auto& camComp = c[0]; + + //void process_arcball(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) { + + // see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/ + glm::vec4 position(camTransform.origin(), 1.0f); + glm::vec4 pivot(camComp.target, 1.0f); + glm::mat4 view = glm::lookAt( camTransform.origin(), camComp.target, camTransform.up() ); glm::vec3 viewDir = -glm::transpose(view)[2]; glm::vec3 rightDir = glm::transpose(view)[0]; @@ -105,7 +123,7 @@ void process_arcball(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx:: rotationMatrixY = glm::rotate(rotationMatrixY, yAngle, rightDir ); glm::vec3 finalPos = ( rotationMatrixY * ( position - pivot ) ) + pivot; - camTransform->origin( finalPos ); + camTransform.origin( finalPos ); } constexpr float ROT_SPEED = 0.05f; @@ -190,6 +208,9 @@ int main(int argc, char* argv[]) { fggl::data::Storage storage; discover( storage.resolvePath(fggl::data::Data, "res") ); + // Hacks to make ECS work, should probably be fixed... + CameraHacks hacks{win, fggl::gfx::Input::instance()}; + fggl::gfx::ShaderCache cache(storage); fggl::gfx::ShaderConfig config; @@ -214,42 +235,42 @@ int main(int argc, char* argv[]) { auto shaderNormals = cache.load( configNormals ); // create ECS - fggl::ecs::ECS ecs; - ecs.registerComponent<fggl::gfx::MeshToken>(); - ecs.registerComponent<fggl::gfx::Camera>(); - ecs.registerComponent<fggl::math::Transform>(); + fggl::ecs2::World world; + world.import<fggl::components::Math>(); + world.import<fggl::components::Gfx>(); + + //fggl::ecs::ECS ecs; + //ecs.registerComponent<fggl::gfx::MeshToken>(); + //ecs.registerComponent<fggl::gfx::Camera>(); + //ecs.registerComponent<fggl::math::Transform>(); // make camera - auto camEnt = ecs.createEntity(); + auto camEnt = world.create("camera"); { - auto cameraTf = ecs.addComponent<fggl::math::Transform>(camEnt); - cameraTf->origin( glm::vec3(0.0f, 3.0f, 3.0f) ); + camEnt.set<fggl::components::Transform>({}); + auto trans = camEnt.get_mut<fggl::components::Transform>(); + trans->origin( glm::vec3(0.0f, 3.0f, 3.0f) ); - ecs.addComponent<fggl::gfx::Camera>(camEnt); + camEnt.set<fggl::components::Camera>({}); } - auto floorEnt = ecs.createEntity(); + auto floorEnt = world.create(); { - ecs.addComponent<fggl::math::Transform>( floorEnt ); + floorEnt.set<fggl::math::Transform>( {} ); fggl::data::Mesh mesh = fggl::data::make_quad_xz(); - auto token = meshRenderer.upload( mesh ); - auto bounds = ecs.addComponent<fggl::gfx::MeshToken>(floorEnt, token); + floorEnt.set<fggl::components::GfxToken>( { } ); } - int nCubes = 3; - int nSections = 2; - constexpr float HALF_PI = M_PI / 2.0f; + constexpr int nSections = 2; + auto bunkerPrototype = world.prefab("bunker"); + { + bunkerPrototype.add_owned<fggl::components::Transform>(); + bunkerPrototype.add_owned<fggl::components::GfxMat>(); - for ( int i=0; i<nCubes; i++ ) { - auto entity = ecs.createEntity(); - - // set the position - auto result = ecs.addComponent<fggl::math::Transform>(entity); - result->origin( glm::vec3( i * 5.0f, 0.0f, 0.0f) ); - + // procedural mesh fggl::data::Mesh mesh; for (int i=-(nSections/2); i<=nSections/2; i++) { const auto shapeOffset = glm::vec3( 0.0f, 0.0f, i * 1.0f ); @@ -266,15 +287,89 @@ int main(int argc, char* argv[]) { fggl::data::make_slope( mesh, leftSlope ); fggl::data::make_slope( mesh, rightSlope ); } - mesh.removeDups(); + + // upload mesh to GPU and get a rendering token auto token = meshRenderer.upload( mesh ); token.pipeline = shaderPhong; - ecs.addComponent<fggl::gfx::MeshToken>(entity, token); + + // add the rendering token to the prototype + bunkerPrototype.set<fggl::components::GfxToken>( token ); } + // create testing bunkers + int nCubes = 3; + for ( int i=0; i<nCubes; i++ ) { + auto bunker = world.create(bunkerPrototype); + //bunker.add<fggl::components::GfxMat>(); + + auto trans = bunker.get_mut<fggl::components::Transform>(); + trans->origin( glm::vec3( i * 5.0f, 0.0f, 0.0f) ); + } + + + // rendering systems - Game/OpenGL + auto matSys = world.ecs().system<const fggl::components::Transform, fggl::components::GfxMat>() + .iter([&world](flecs::iter& it, const fggl::components::Transform* t, fggl::components::GfxMat* mats) { + + std::cerr << "splating entity data" << std::endl; + + // there is probably a more ECS-friendly way of doing this... + auto cam = world.ecs().lookup("camera"); + const auto camComp = cam.get< fggl::components::Camera >(); + const auto camTrans = cam.get< fggl::components::Transform >(); + + // build the two matrices from the camera + const fggl::math::mat4 proj = glm::perspective( camComp->fov, camComp->aspectRatio, camComp->nearPlane, camComp->farPlane); + const fggl::math::mat4 view = glm::lookAt( camTrans->origin(), camComp->target, camTrans->up() ); + + // splat component data into mesh data + for ( auto i : it ) { + mats[i].proj = proj; + mats[i].view = view; + mats[i].model = t[i].model(); + } + }); + + glm::vec3 lightPos(20.0f, 20.0f, 15.0f); + auto drawSys = world.ecs().system<>("DrawSys", "gfx.mat,SHARED:gfx.token") + .iter([lightPos](flecs::iter& it) { + + auto t = it.column<const fggl::components::GfxMat>(1); + auto mesh = it.column<const fggl::components::GfxToken>(2); + + std::cerr << "1: " << t.is_set() << std::endl; + std::cerr << "2: " << t.is_set() << std::endl; + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + glEnable(GL_DEPTH_TEST); + + for ( auto i : it ) { + auto shader = mesh->pipeline; + glUseProgram( shader ); + + glUniformMatrix4fv( glGetUniformLocation(shader, "model"), 1, GL_FALSE, glm::value_ptr( t[i].model ) ); + glUniformMatrix4fv( glGetUniformLocation(shader, "view"), 1, GL_FALSE, glm::value_ptr( t[i].view ) ); + glUniformMatrix4fv( glGetUniformLocation(shader, "projection"), 1, GL_FALSE, glm::value_ptr( t[i].proj ) ); + + // lighting + GLint lightID = glGetUniformLocation(shader, "lightPos"); + if ( lightID != -1 ) + glUniform3fv( lightID, 1, glm::value_ptr( lightPos ) ); + + glBindVertexArray( mesh->vao ); + glDrawElements( GL_TRIANGLES, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) ); + } + }); + fggl::gfx::Input& input = fggl::gfx::Input::instance(); + world.ecs().system<fggl::components::Transform, fggl::components::Camera>() + .ctx( &hacks ) + .iter( CameraArcball ); + bool joystickWindow = true; bool gamepadWindow = true; @@ -289,14 +384,15 @@ int main(int argc, char* argv[]) { // update step time += dt; - process_camera(win, ecs, input, camEnt); + + //process_camera(win, ecs, input, camEnt); if ( cam_mode == cam_arcball ) { if ( input.mouseDown( fggl::gfx::MOUSE_2 ) ) { - process_arcball(win, ecs, input, camEnt); +// process_arcball(win, ecs, input, camEnt); } - } else if ( cam_mode == cam_free ) { + }/* else if ( cam_mode == cam_free ) { process_freecam(win, ecs, input, camEnt); - } + }*/ // imgui joystick debug ImGui::Begin("Joysticks", &joystickWindow); @@ -406,22 +502,28 @@ int main(int argc, char* argv[]) { // render step ogl.clear(); + world.tick(dt); + + // rendering systems + matSys.run(dt); + drawSys.run(dt); + // render using real shader - auto renderables = ecs.getEntityWith<fggl::gfx::MeshToken>(); +/* auto renderables = ecs.getEntityWith<fggl::gfx::MeshToken>(); for ( auto renderable : renderables ) { auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable); token->pipeline = shaderPhong; } - meshRenderer.render(win, ecs, camEnt, 16.0f); + meshRenderer.render(win, ecs, camEnt, 16.0f);*/ // render using normals shader - if ( showNormals ) { +/* if ( showNormals ) { for ( auto renderable : renderables ) { auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable); token->pipeline = shaderNormals; } meshRenderer.render(win, ecs, camEnt, 16.0f); - } + }*/ debug.draw(); win.swap(); diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index 86599c8dc5349dd920e617468966bae8eb57ceb5..ffc795c92f2e3ae72fba3db93edb3f6e3fc8493c 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -7,6 +7,9 @@ add_library(fggl fggl.cpp ) target_include_directories(fggl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../) +# ECS +add_subdirectory(ecs2) + # Graphics backend add_subdirectory(gfx) target_link_libraries(fggl glfw) diff --git a/fggl/ecs2/CMakeLists.txt b/fggl/ecs2/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..eecf3a503b11699da4d2ca68dbcb43ce306c289f --- /dev/null +++ b/fggl/ecs2/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_subdirectory(flecs) + +target_sources(fggl + PRIVATE + ecs.cpp +) +target_link_libraries(fggl flecs) diff --git a/fggl/ecs2/ecs.cpp b/fggl/ecs2/ecs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e563a025d08bd725e8876594b1e6e63e4571b60b --- /dev/null +++ b/fggl/ecs2/ecs.cpp @@ -0,0 +1,31 @@ +#include "ecs.hpp" + +using fggl::ecs2::World; +using fggl::ecs2::Entity; + +World::World() { +} + +Entity World::prefab(const char *name) { + return m_ecs.prefab( name ); +} + +Entity World::create() { + return m_ecs.entity(); +} + +Entity World::create(const Entity prefab) { + auto newEnt = create(); + newEnt.is_a(prefab); + return newEnt; +} + +Entity World::create(const char* name) { + return m_ecs.entity(name); +} + +Entity World::create(const char* name, const Entity prefab) { + auto newEnt = create(name); + newEnt.is_a( prefab ); + return newEnt; +} diff --git a/fggl/ecs2/ecs.hpp b/fggl/ecs2/ecs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..78c74d37395312e7ff01bfea7565e455d5e84535 --- /dev/null +++ b/fggl/ecs2/ecs.hpp @@ -0,0 +1,43 @@ +#ifndef FGGL_ECS2_ECS_H +#define FGGL_ECS2_ECS_H + +#include "flecs.h" + +namespace fggl::ecs2 { + namespace impl = flecs; + using Entity = flecs::entity; + + class World { + public: + World(); + ~World() = default; + + Entity prefab(const char* name); + + Entity create(); + Entity create(const Entity prefab); + + Entity create(const char* name); + Entity create(const char* name, const Entity prefab); + + inline bool tick(float dt) { + return m_ecs.progress(dt); + } + + template<typename T> + void import() { + m_ecs.import<T>(); + } + + // ecs escape hatch + impl::world& ecs() { + return m_ecs; + } + + private: + impl::world m_ecs; + }; + +}; + +#endif diff --git a/fggl/ecs2/flecs/BUILD b/fggl/ecs2/flecs/BUILD new file mode 100644 index 0000000000000000000000000000000000000000..f940b57cec4bb2124541af76e9bc66fbbcf59394 --- /dev/null +++ b/fggl/ecs2/flecs/BUILD @@ -0,0 +1,9 @@ + +cc_library( + name = "flecs", + visibility = ["//visibility:public"], + + srcs = glob(["src/**/*.c", "src/**/*.h"]), + hdrs = glob(["include/**/*.h", "include/**/*.hpp"]), + includes = ["include"], +) diff --git a/fggl/ecs2/flecs/CHANGELOG.md b/fggl/ecs2/flecs/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..4df9905fcd331facd3796fd5d3f6ff1ee99e87ef --- /dev/null +++ b/fggl/ecs2/flecs/CHANGELOG.md @@ -0,0 +1,142 @@ +flecs +CHANGELOG +======================================================================= + +RELEASE NOTES: https://github.com/SanderMertens/flecs/releases +REPO: https://github.com/SanderMertens/flecs/commits/master +COMMUNITY: https://discord.gg/MRSAZqb +DOCS: https://flecs.docsforge.com/ +FAQ: https://github.com/SanderMertens/flecs/blob/master/docs/FAQ.md +QUICKSTART: https://github.com/SanderMertens/flecs/blob/master/docs/Quickstart.md +MANUAL: https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md + +----------------------------------------------------------------------- + VERSION 2.3.2 (Released 2021-02-23) +----------------------------------------------------------------------- + +## Improvements +- replace iostream with initializer_list (C++ API, thanks @ikrima!) +- ensure entity::m_id is initialized to 0 (C++ API, thanks @ikrima!) +- use ecs_os_malloc instead of new (C++ API, thanks @ikrima!) +- remove superfluous copies of lambda functions (C++ API, thanks @ikrima!) +- add CHANGELOG (thanks @ikrima!) + +## Bugfixes +- fix matching for queries with shared componnents when base entity is deleted +- fix various issues with component registration in C++ +- fix issue with setting target FPS on Apple M1 (thanks @prime31!) +- fix issues with CMake file (thanks @Spacelm!) +- fix crash when creating & deleting queries +- guarantee that id returned by new_component_id is unused + + +----------------------------------------------------------------------- + VERSION 2.3.1 (Released 2021-02-02) +----------------------------------------------------------------------- + +## Improvements +- Improved lerp example +- Added OS API example C++ (thanks @mcmlevi!) +- Upgraded cmake buildfiles (thanks @rawbby!) +- Clarified text in README describing API/ABI stability + +## Bugfixes +- Fix crash when using overlapping UnSet systems +- Fix issue with passing incorrect row to UnSet systems +- Added .hpp files to cmake install +- Fixed issue with using get_mut with traits + + +----------------------------------------------------------------------- + VERSION 2.3.0 (Released 2021-01-17) +----------------------------------------------------------------------- + +## Highlights +- Big performance improvements & reduced memory usage for applications with lots of tables, such as when using hierarchies +- Component enabling/disabling allows for quick component mutations without moving entities between archetypes +- New statistics addon for retrieving metrics from running world (replaces stats module) + +Thanks to @randy408, @sh-dave, @kevinresol, @jasonliang-dev and @Alexandre-P-J for submitting PRs! 🎉 + +Thanks to @ikrima and @jtferson for writing two awesome testimonials on using Flecs with Unreal Engine 4 🙠: +- https://bebylon.dev/blog/ecs-flecs-ue4/ +- https://jtferson.github.io/blog/flecs_and_unreal/ + +Thanks to the new Flecs sponsors â¤ï¸ : + - @Zifkan + - @TFlippy + - @Hexlord + +## Breaking changes +- calling ecs_get for a tag will no longer return NULL, but will assert +- statistics module is removed in favor of easier to use statistics addon +- unused table_offset member is removed from iterator + +## Deprecated features +- ecs_entity() macro is now deprecated, use ecs_id() + +## Features +- Direct access addon, which provides fast access to underlying storage +- Added singleton API to C++ world class +- Added orphaning for subqueries, which allows an application to check if a parent query still exists +- Added ecs_get_typeid function to get the component id for traits +- The type used for time keeping is now customizable +- New statistics addon for retrieving metrics from running world +- Added get_parent function that accepts entity/tag +- Added component enabling/disabling +- Added support for sorting on shared components + +## Improvements +- Improved ecs_delete performance (reduced entity index accesses from 2 to 1) +- C & C++ code compiles warning-free with more warning flags, for more compiler versions & more platforms +- Improved error messages when application passes invalid entity id +- Made include paths relative so source code is easier to integrate with bazel +- Fixed typos in documentation +- Improve error message when trying to add 0 as base entity (using ECS_INSTANCEOF) +- Add check for conflicting source modifier in OR expressions +- Extended documentation, added new examples and fixed typos +- Removed dead/redundant code +- Improved performance of ecs_get +- Add sanitizer & custom builds to CI, remove Travis CI builds +- Don't add Name component when provided name for entity is NULL +- Allow flecs::system instances to be (re)created from entity id +- Added godbolt "try online" badge to README +- Improve allocation strategy of vector datastructure +- Improved performance for checking if entity id is a number +- Added missing query functions to C++ API for sorting +- Improved performance of table creation by two orders of magnitude +- Reduced memory footprint of table graph by a factor 60 +- Added example to demonstrate world merging with direct access API +- Added more inline documentation for datastructures +- Added assert when trying to instantiate child as instance +- Improve errors when invalid operation is invoked on iterator +- Throw assert when invoking ecs_modified on entity that does not have the component +- Allow for type lookups in systems, as long as the type already exists +- Add return types to ecs_defer_begin and ecs_defer_end to obtain defer status of world +- Remove redundant table_offset member from ecs_iter_t +- Improved portability of POSIX OS API example + +## Bugfixes +- Fixed issues with subqueries and query rematching +- Corrected wrong return type of ecs_term_size (was ecs_entity_t, is now size_t) +- Add missing \0 when appending to application buffer with strbuf datastructure +- Fixed crash when instantiating an empty prefab +- Fixed issue with shared tags and queries using each() in the C++ API +- Fixed issue with instantiating empty child table +- Fixed issue with ecs_bulk_new and traits +- Fixed issue when using ecs_id_str with small buffers +- Fixed bug when adding trait to entity with switch +- Fixed name conflicts in amalgamated source +- Fixed path lookup in ecs_import +- Fixed EXPORT macro's in OS API examples +- Fixed issue with restoring worlds that have recycled ids +- Added missing EXPORT macro's to API functions +- Fixed assert after deleting entities from restored world +- Removed obsolete assert from ecs_modified +- Added missing statement to finalize system in C++ +- Fixed issues with removing case values +- Fixed issues in C++ API with implicit component registration +- Fixed assert when removing from switch list +- Fixed bug where obtaining type from entity would generate a new entity id +- Fixed incorrect description of ecs_delete in manual +- Fixed issues with custom builds diff --git a/fggl/ecs2/flecs/CMakeLists.txt b/fggl/ecs2/flecs/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f9ea2a9ff7bd056c89aa1ca93ef40b3cfd645818 --- /dev/null +++ b/fggl/ecs2/flecs/CMakeLists.txt @@ -0,0 +1,85 @@ +cmake_minimum_required(VERSION 3.1) +cmake_policy(SET CMP0063 NEW) + +project(flecs LANGUAGES C) + +option(FLECS_STATIC_LIBS "Build static flecs lib" ON) +option(FLECS_PIC "Compile static flecs lib with position-independent-code (PIC)" ON) +option(FLECS_SHARED_LIBS "Build shared flecs lib" ON) +option(FLECS_DEVELOPER_WARNINGS "Enable more warnings" OFF) + +if(NOT FLECS_STATIC_LIBS AND NOT FLECS_SHARED_LIBS) + message(FATAL_ERROR "At least one of FLECS_STATIC_LIBS or FLECS_SHARED_LIBS options must be enabled") +endif() + +# include utilities for compiler options and warnings + +include(cmake/target_default_compile_warnings.cmake) +include(cmake/target_default_compile_options.cmake) + +# collect all include and source files +# (use the manual list to improve ide support) + +file(GLOB_RECURSE INC include/*.h include/*.hpp) + +file(GLOB_RECURSE SRC src/*.c) + +set(FLECS_TARGETS "") + +# build the shared library +if(FLECS_SHARED_LIBS) + add_library(flecs SHARED ${INC} ${SRC}) + add_library(flecs::flecs ALIAS flecs) + + target_default_compile_options_c(flecs) + if(FLECS_DEVELOPER_WARNINGS) + target_default_compile_warnings_c(flecs) + endif() + + target_include_directories(flecs PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include>) + + list(APPEND FLECS_TARGETS flecs) +endif() + +# build the static library +if(FLECS_STATIC_LIBS) + add_library(flecs_static STATIC ${INC} ${SRC}) + add_library(flecs::flecs_static ALIAS flecs_static) + + target_default_compile_options_c(flecs_static) + if(FLECS_DEVELOPER_WARNINGS) + target_default_compile_warnings_c(flecs_static) + endif() + + if(FLECS_PIC) + set_property(TARGET flecs_static PROPERTY POSITION_INDEPENDENT_CODE ON) + endif() + + target_include_directories(flecs_static PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include>) + target_compile_definitions(flecs_static PUBLIC flecs_STATIC) + + list(APPEND FLECS_TARGETS flecs_static) +endif() + +# define the install steps +include(GNUInstallDirs) +install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp") + +install(TARGETS ${FLECS_TARGETS} + EXPORT flecs-export + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install(EXPORT flecs-export + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/flecs + NAMESPACE flecs:: + FILE flecs-config.cmake) diff --git a/fggl/ecs2/flecs/LICENSE b/fggl/ecs2/flecs/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8d7b5a07af40f016d0a870e356cdd8cd7a36fbb1 --- /dev/null +++ b/fggl/ecs2/flecs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Sander Mertens + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/fggl/ecs2/flecs/README.md b/fggl/ecs2/flecs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ea9fdaa54cfb4c11380e845288d18e746936af84 --- /dev/null +++ b/fggl/ecs2/flecs/README.md @@ -0,0 +1,243 @@ + + +[](https://github.com/SanderMertens/flecs/actions) +[](https://codecov.io/gh/SanderMertens/flecs) +[](https://discord.gg/BEzP5Rgrrp) +[](https://godbolt.org/z/bs11T3) +[](http://flecs.docsforge.com/) + +Flecs is a fast and lightweight Entity Component System with a focus on high performance game development and usability ([join the Discord!](https://discord.gg/MRSAZqb)). The highlights of the framework are: + +- Zero dependency C99 core, modern type safe C++11 API, no dependencies on STL types +- Batched iteration with direct access to component arrays +- SoA/Archetype storage for efficient CPU caching & vectorization +- Automatic component registration across binaries +- Runtime tags +- Hierarchies +- Prefabs +- Relationships graphs & graph queries +- Thread safe, lockless API +- Systems that are ran manually, every frame or at a time/rate interval +- Queries that can be iterated from free functions +- Modules for organizing components & systems +- Builtin statistics & introspection +- Modular core with compile-time disabling of optional features +- A dashboard module for visualizing statistics: + +<img width="942" alt="Screen Shot 2020-12-02 at 1 28 04 AM" src="https://user-images.githubusercontent.com/9919222/100856510-5eebe000-3440-11eb-908e-f4844c335f37.png"> + +## What is an Entity Component System? +ECS (Entity Component System) is a design pattern used in games and simulations that produces fast and reusable code. Dynamic composition is a first-class citizen in ECS, and there is a strict separation between data and behavior. A framework is an Entity Component System if it: + +- Has _entities_ that are unique identifiers +- Has _components_ that are plain data types +- Has _systems_ which are behavior matched with entities based on their components + +## Documentation +If you are still learning Flecs, these resources are a good start: +- [Flecs not for dummies (presentation)](https://github.com/SanderMertens/flecs_not_for_dummies) +- [Quickstart](docs/Quickstart.md) ([docsforge](https://flecs.docsforge.com/master/quickstart/)) +- [Designing with Flecs](docs/DesignWithFlecs.md) ([docsforge](https://flecs.docsforge.com/master/designing-with-flecs/)) + +The FAQ is where some of the most asked questions are listed: +- [FAQ](docs/FAQ.md) ([docsforge](https://flecs.docsforge.com/master/faq/)) + +The manual and examples come in handy if you're looking for information on specific features: +- [Manual](docs/Manual.md) ([docsforge](https://flecs.docsforge.com/master/manual/)) +- [C examples](examples/c) +- [C++ examples](examples/cpp) + +If you are migrating from Flecs v1 to v2, check the migration guide: +- [Migration guide](docs/MigrationGuide.md) ([docsforge](https://flecs.docsforge.com/master/migrationguide/)) + +Here is some awesome content provided by the community (thanks everyone! :heart:): +- [Bringing Flecs to UE4](https://bebylon.dev/blog/ecs-flecs-ue4/) +- [Flecs + UE4 is magic](https://jtferson.github.io/blog/flecs_and_unreal/) +- [Quickstart with Flecs in UE4](https://jtferson.github.io/blog/quickstart_with_flecs_in_unreal_part_1/) +- [Automatic component registration in UE4](https://jtferson.github.io/blog/automatic_flecs_component_registration_in_unreal/) +- [Building a space battle with Flecs in UE4](https://twitter.com/ajmmertens/status/1361070033334456320) +- [Flecs + SDL + Web ASM example](https://github.com/HeatXD/flecs_web_demo) ([live demo](https://heatxd.github.io/flecs_web_demo/)) +- [Flecs + gunslinger example](https://github.com/MrFrenik/gs_examples/blob/main/18_flecs/source/main.c) + +## Examples +This is a simple flecs example in the C99 API: + +```c +typedef struct { + float x, y; +} Position, Velocity; + +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *ecs = ecs_init(); + + ECS_COMPONENT(ecs, Position); + ECS_COMPONENT(ecs, Velocity); + + ECS_SYSTEM(ecs, Move, EcsOnUpdate, Position, Velocity); + + ecs_entity_t e = ecs_new_id(ecs); + ecs_set(ecs, e, Position, {10, 20}); + ecs_set(ecs, e, Velocity, {1, 2}); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { ecs_id(Position) }); + while (ecs_term_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + for (int i = 0; i < it.count; i ++) { + printf("{%f, %f}\n", p[i].x, p[i].y); + } + } + + while (ecs_progress(ecs, 0)) { } +} +``` + +This is the same example in the C++11 API: + +```c++ +struct Position { + float x, y; +}; + +struct Velocity { + float x, y; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs; + + ecs.system<Position, const Velocity>() + .each([](Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + auto e = ecs.entity() + .set([](Position& p, Velocity& v) { + p = {10, 20}; + v = {1, 2}; + }); + + ecs.each([](flecs::entity e, Position& p) { + std::cout << "{" << p.x << ", " << p.y << "}" << std::endl; + }); + + while (ecs.progress()) { } +} +``` + +The first C example used macro's to emulate a type-safe layer on top of the +underlying generic API. This example shows the C API without macro's: + +```c +// Register the Position component +ecs_entity_t pos = ecs_component_init(ecs, &(ecs_component_desc_t){ + .entity.name = "Position", + .size = sizeof(Position), .alignment = ECS_ALIGNOF(Position) +}); + +// Register the Velocity component +ecs_entity_t vel = ecs_component_init(ecs, &(ecs_component_desc_t){ + .entity.name = "Velocity", + .size = sizeof(Velocity), .alignment = ECS_ALIGNOF(Velocity) +}); + +// Create the Move system +ecs_system_init(ecs, &(ecs_system_desc_t){ + .entity = { .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.terms = {{pos}, {vel, .inout = EcsIn}}, + .callback = Move, +}); + +// Create entity +ecs_entity_t e = ecs_new_id(ecs); + +// Set components +ecs_set_id(ecs, e, pos, sizeof(Position), &(Position){10, 20}); +ecs_set_id(ecs, e, vel, sizeof(Velocity), &(Velocity){1, 2}); +``` + +## Building +The easiest way to add Flecs to a project is to add [flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.c) and [flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.h) to your source code. These files can be added to both C and C++ projects (the C++ API is embedded in flecs.h). Alternatively you can also build Flecs as a library by using the cmake, meson, bazel or bake buildfiles. + +### Custom builds +The Flecs source has a modular design which makes it easy to strip out code you don't need. At its core, Flecs is a minimalistic ECS library with a lot of optional features that you can choose to include or not. [This section of the manual](https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md#custom-builds) describes how to customize which features to include. + +## Software Quality +To ensure stability of Flecs, the code is thoroughly tested on every commit: + +- More than 2400 testcases and 60.000 lines of test code +- Over 90% code coverage +- All tests run without memory leaks & memory corruption +- All examples are compiled warning free + +The code is validated on the following platforms/compilers: + +- **Windows** + - msvc +- **Ubuntu** + - gcc 7, 8, 9, 10 + - clang 8, 9 +- **MacOS** + - gcc 10 + - clang 9 + +### API stability +APIs are stable between minor and patch versions, but exceptions are made in these scenarios: +- The design of an API prevents it from being used without introducing bugs +- The design of an API is prone to misuse or confusing + +The following parts of the API are not stable between patch/minor versions: +- Anything in include/private +- The ABI is not guaranteed to be stable, so a recompile of code is required after upgrading + +Functions may become deprecated before a major release. To build flecs without deprecated functions, exclude the `FLECS_DEPRECATED` addon. (see [custom builds](https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md#custom-builds)). + +## Modules +The following modules are available in [flecs-hub](https://github.com/flecs-hub). Note that modules are mostly intended as example code, and their APIs may change at any point in time. + +Module | Description +------------|------------------ +[flecs.meta](https://github.com/flecs-hub/flecs-meta) | Reflection for Flecs components +[flecs.json](https://github.com/flecs-hub/flecs-json) | JSON serializer for Flecs components +[flecs.rest](https://github.com/flecs-hub/flecs-rest) | A REST interface for introspecting & editing entities +[flecs.player](https://github.com/flecs-hub/flecs-player) | Play, stop and pause simulations +[flecs.monitor](https://github.com/flecs-hub/flecs-monitor) | Web-based monitoring of statistics +[flecs.dash](https://github.com/flecs-hub/flecs-dash) | Web-based dashboard for remote monitoring and debugging of Flecs apps +[flecs.components.input](https://github.com/flecs-hub/flecs-components-input) | Components that describe keyboard and mouse input +[flecs.components.transform](https://github.com/flecs-hub/flecs-components-transform) | Components that describe position, rotation and scale +[flecs.components.physics](https://github.com/flecs-hub/flecs-components-physics) | Components that describe physics and movement +[flecs.components.geometry](https://github.com/flecs-hub/flecs-components-geometry) | Components that describe geometry +[flecs.components.graphics](https://github.com/flecs-hub/flecs-components-graphics) | Components used for computer graphics +[flecs.components.gui](https://github.com/flecs-hub/flecs-components-gui) | Components used to describe GUI components +[flecs.components.http](https://github.com/flecs-hub/flecs-components-http) | Components describing an HTTP server +[flecs.systems.transform](https://github.com/flecs-hub/flecs-systems-transform) | Hierarchical transforms for scene graphs +[flecs.systems.sdl2](https://github.com/flecs-hub/flecs-systems-sdl2) | SDL window creation & input management +[flecs.systems.sokol](https://github.com/flecs-hub/flecs-systems-sokol) | Sokol-based renderer +[flecs.systems.civetweb](https://github.com/flecs-hub/flecs-systems-civetweb) | A civetweb-based implementation of flecs.components.http + +## Language bindings +- [Lua](https://github.com/flecs-hub/flecs-lua) +- [Zig](https://github.com/prime31/zig-flecs) + +## Useful Links +- [ECS FAQ](https://github.com/SanderMertens/ecs-faq) +- [Medium](https://ajmmertens.medium.com) +- [Twitter](https://twitter.com/ajmmertens) +- [Reddit](https://www.reddit.com/r/flecs) + +## Supporting Flecs +Supporting Flecs goes a long way towards keeping the project going and the community alive! If you like the project, consider: +- Giving it a star +- Becoming a sponsor: https://github.com/sponsors/SanderMertens + +Thanks in advance! diff --git a/fggl/ecs2/flecs/WORKSPACE b/fggl/ecs2/flecs/WORKSPACE new file mode 100644 index 0000000000000000000000000000000000000000..34536ebfa8990c19de31dfb9904e146b35dcd598 --- /dev/null +++ b/fggl/ecs2/flecs/WORKSPACE @@ -0,0 +1,52 @@ +load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") + +new_git_repository( + name = "bake", + remote = "git@github.com:SanderMertens/bake.git", + commit = "cfc90745f9daa7b7fba80f229af18cdd5029b066", + shallow_since = "1614835160 -0800", + + build_file_content = """ +cc_library( + name = "driver-test", + visibility = ["//visibility:public"], + deps = [":util", ":bake"], + + srcs = glob(["drivers/test/src/**/*.c", "drivers/test/src/**/*.h"]), + hdrs = glob(["drivers/test/include/**/*.h"]), + includes = ["drivers/test/include"], +) + +cc_library( + name = "bake", + visibility = ["//visibility:public"], + deps = [":util"], + + srcs = glob(["src/*.c", "src/*.h"]), + hdrs = glob(["include/*.h", "include/bake/*.h"]), + includes = ["include"], +) + +cc_library( + name = "util", + visibility = ["//visibility:public"], + defines = ["__BAKE__", "_XOPEN_SOURCE=600"], + + linkopts = select({ + "@bazel_tools//src/conditions:windows": [], + "//conditions:default": ["-lrt -lpthread -ldl"], + }), + + srcs = glob(["util/src/*.c"]) + select({ + "@bazel_tools//src/conditions:windows": glob(["util/src/win/*.c"]), + "//conditions:default": glob(["util/src/posix/*.c"]), + }), + hdrs = glob(["util/include/*.h", "util/include/bake-util/*.h"]) + select({ + "@bazel_tools//src/conditions:windows": glob(["util/include/bake-util/win/*.h"]), + "//conditions:default": glob(["util/include/bake-util/posix/*.h"]), + }), + includes = ["util/include"], +) +""" +) + diff --git a/fggl/ecs2/flecs/cmake/target_default_compile_options.cmake b/fggl/ecs2/flecs/cmake/target_default_compile_options.cmake new file mode 100644 index 0000000000000000000000000000000000000000..91494c54db8d342aa29b664e9615775d340fdc1e --- /dev/null +++ b/fggl/ecs2/flecs/cmake/target_default_compile_options.cmake @@ -0,0 +1,28 @@ +# this seems redundant as the target +# property is set. but for apple clang +# this seems to be needed + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +function(target_default_compile_options_c THIS) + + set_target_properties(${THIS} PROPERTIES + LINKER_LANGUAGE C + C_STANDARD 99 + C_STANDARD_REQUIRED ON + C_VISIBILITY_PRESET hidden) + +endfunction() + +function(target_default_compile_options_cxx THIS) + + set_target_properties(${THIS} PROPERTIES + LINKER_LANGUAGE CXX + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON) + +endfunction() diff --git a/fggl/ecs2/flecs/cmake/target_default_compile_warnings.cmake b/fggl/ecs2/flecs/cmake/target_default_compile_warnings.cmake new file mode 100644 index 0000000000000000000000000000000000000000..2f65ecfa6325c7ecb11b27a11be4b7abc1fbecd4 --- /dev/null +++ b/fggl/ecs2/flecs/cmake/target_default_compile_warnings.cmake @@ -0,0 +1,113 @@ +function(target_default_compile_warnings_c THIS) + + if (CMAKE_C_COMPILER_ID STREQUAL "Clang" + OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + + target_compile_options(${THIS} PRIVATE + #$<$<CONFIG:RELEASE>:-Werror> + $<$<CONFIG:Debug>:-Wshadow> + $<$<CONFIG:Debug>:-Wunused> + -Wall -Wextra + -Wcast-align + -Wpedantic + -Wconversion + -Wsign-conversion + -Wdouble-promotion + -Wformat=2) + + elseif (CMAKE_C_COMPILER_ID STREQUAL "GNU") + + target_compile_options(${THIS} PRIVATE + #$<$<CONFIG:RELEASE>:-Werror> + $<$<CONFIG:Debug>:-Wshadow> + $<$<CONFIG:Debug>:-Wunused> + -Wall -Wextra + -Wcast-align + -Wpedantic + -Wconversion + -Wsign-conversion + -Wdouble-promotion + -Wformat=2) + + elseif (CMAKE_C_COMPILER_ID STREQUAL "MSVC") + + target_compile_options(${THIS} PRIVATE + #$<$<CONFIG:RELEASE>:/WX> + /W4 + /w14242 /w14254 /w14263 + /w14265 /w14287 /we4289 + /w14296 /w14311 /w14545 + /w14546 /w14547 /w14549 + /w14555 /w14619 /w14640 + /w14826 /w14905 /w14906 + /w14928) + + else () + + message(WARNING + "No Warnings specified for ${CMAKE_C_COMPILER_ID}. " + "Consider using one of the following compilers: Clang, GNU, MSVC.") + + endif () + +endfunction() + +function(target_default_compile_warnings_cxx THIS) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + + target_compile_options(${THIS} PRIVATE + #$<$<CONFIG:RELEASE>:-Werror> + $<$<CONFIG:Debug>:-Wshadow> + $<$<CONFIG:Debug>:-Wunused> + -Wall -Wextra + -Wnon-virtual-dtor + -Wold-style-cast + -Wcast-align + -Woverloaded-virtual + -Wpedantic + -Wconversion + -Wsign-conversion + -Wdouble-promotion + -Wformat=2) + + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + + target_compile_options(${THIS} PRIVATE + #$<$<CONFIG:RELEASE>:-Werror> + $<$<CONFIG:Debug>:-Wshadow> + $<$<CONFIG:Debug>:-Wunused> + -Wall -Wextra + -Wnon-virtual-dtor + -Wold-style-cast + -Wcast-align + -Woverloaded-virtual + -Wpedantic + -Wconversion + -Wsign-conversion + -Wdouble-promotion + -Wformat=2) + + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + + target_compile_options(${THIS} PRIVATE + #$<$<CONFIG:RELEASE>:/WX> + /W4 + /w14242 /w14254 /w14263 + /w14265 /w14287 /we4289 + /w14296 /w14311 /w14545 + /w14546 /w14547 /w14549 + /w14555 /w14619 /w14640 + /w14826 /w14905 /w14906 + /w14928) + + else () + + message(WARNING + "No Warnings specified for ${CMAKE_CXX_COMPILER_ID}. " + "Consider using one of the following compilers: Clang, GNU, MSVC, AppleClang.") + + endif () + +endfunction() diff --git a/fggl/ecs2/flecs/codecov.yaml b/fggl/ecs2/flecs/codecov.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fc77636a69392ab58e69ab88d54cd3730bd3fee6 --- /dev/null +++ b/fggl/ecs2/flecs/codecov.yaml @@ -0,0 +1,21 @@ +codecov: + notify: + require_ci_to_pass: no + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no diff --git a/fggl/ecs2/flecs/docs/DesignWithFlecs.md b/fggl/ecs2/flecs/docs/DesignWithFlecs.md new file mode 100644 index 0000000000000000000000000000000000000000..73941b3ee4689e645a0cc48740b235eeda4cb220 --- /dev/null +++ b/fggl/ecs2/flecs/docs/DesignWithFlecs.md @@ -0,0 +1,164 @@ +# Designing with Flecs +Designing games with ECS can be overwhelming and quite different from object oriented approaches, not to mention learning all the framework specific features! This guide provides a few quick tips for using the features provided by Flecs to build fast, readable and reusable code. + +Note that these are my own guidelines, and as a result this is an opinionated document. There are many other approaches to designing with ECS, and it would be silly to claim that this is "the one true way". Take this with a grain of salt, and feel free to deviate when you feel it is appropriate. + +One other note: this document is very light on feature documentation. The point of the document is to provide suggestions for how to design with Flecs features, not document their ins and outs. All of the features listed in this document are described in the manual and have example code. + +## Entities +Entities are probably the easiest thing to get used to. They typically map to in-game objects the same way you would create them in other game engines. + +### Entity Initialization +When creating entities, you typically want to initialize them with a set of default components, and maybe even default values. Flecs introduced prefabs for this use case. Prefabs let you create entity templates that contain components with default values. Creating entities from prefabs is not just an easy way to initialize entities, it is also faster, and helps with classifying the different kinds of entities you have. + +### Entity Lifecycle +Entities can be created and deleted dynamically. When entities are deleted, the existing handles to that entity are no longer valid. When you are working with entity handles, it is often good practice to make sure they are still alive. You can do this with the `is_alive()` function. Other than that, entity handles are stable, which means that you can safely store them in your components or elsewhere. + +### Entity Names +Flecs entities can be named. This makes it easy to identify entities in editors or while debugging, and also allows you to lookup entities by name. While this can be useful, name lookups on the world are expensive! Flecs supports relative name lookups, where you search for an entity name relative to a parent entity, which is much faster. Even so, if you do a lookup by name, it is a good idea to cache the entity handle. + +## Components +Designing your components is probably the most important thing you will do in your ECS application. The reason is that if you change a component you have to update all systems that use it. Fortunately there are ways to design components that refactoring and do not negatively impact performance. + +### Component Size +The most important guideline: keep your components as small and atomic. If you are choosing between a Transform component or separate Position, Rotation, Scale, Matrix components pick the latter. If you have a Turret component with a target and a rotation angle, split them up into two components. + +The reasoning behind this is simple: it is very cheap (at least in Flecs) to query for multiple components at the same time. There is almost no overhead associated with adding more components to a query, due to how queries cache results. + +The second reason is that it improves caching performance. If your system only needs Position, but also has to load all of the other data in Transform, you end up loading a lot of data in your cache that remains unused. This may or may not be a problem, but all else being equal it definitely won't hurt. + +The third reason is that you can only split up your components so far. The number of ways in which you can combine compponents is infinitely larger than the number of ways in which you can split them up, and this translates, in a very practical way, into less refactoring. When you have reduced your components to atomic units of data, there simply isn't anything left to refactor. + +The fourth and last reason why you want to keep your components small is code reusability. If your components are atomic units of data, they are automatically less opinionated than a component that combines more stuff. As a result, it is more likely that you find the component to work well across projects. This trickles down into the rest of your ECS code: systems written for atomic components will also be more reusable across projects. + +A disadvantage of small components is that you get more of them in a project. This can make it harder to find the components a system needs, especially in large projects with hundreds of components. + +### Complex component data +There is a misconception that ECS components can only contain plain data types, and should not have vectors, or more complex data structures. The reality is more nuanced. You may find yourself often needing specialized data structures, and it is perfectly fine to store these in components. + +However, you should ask yourself whether the complexity is _necessary_. For example, if you have a vector with data in a component, would that data be better off as entities? Sometimes the answers to these questions are not black and white. If the elements have individual lifecycles, they might be better stored as entities. If they live and die with the entity, maybe storing them in a vector is fine. If you always need to access the entities in a very specific order, a vector might be better. + +Sometimes you might even do both, where you have a component with a vector of entity handles. Which solution works best really depends on the situation, but know that there are no "wrong" approaches here. If it works well for your application, there should be no dogmatic reasons to not do it. + +## Queries +Queries are the primary method in Flecs for finding the entities for a set of components (or more specifically: a component expression). Queries are easy to use, but there a few things to keep in mind. + +### Cache Your Queries +Creating a query object is expensive, iterating a query is very cheap. Because of this it really pays off to keep your query objects around, and not recreate them each time you have a need for them. Query objects can be iterated as many times as you need. You can even iterate the same query safely across threads. + +### Use in/inout/out Modifiers +Flecs does some analysis based on how you read and write your components, and the more accurate the information it has is, the more efficient your code will run. If you don't specify these modifiers, Flecs assumes `[inout]`. This impacts change tracking (an `inout/out` term of a query invalidates a component when it is iterated), which in turn impacts sorting (which depends on change tracking). It can also introduce more synchronization points than strictly needed. + +Note that if you use C++, and you use templates to create your queries, putting `const` before your component will automatically make it an `[in]` column. + +### Annotations +You can further annotate queries with components that are not matched with the query, but that are written using ECS operations (like add, remove, set etc.). Such operations are automatically deferred and merged at the end of the frame. With annotations you can enforce merging at an earlier point, by specifying that a component modification has been queued. When Flecs sees this, it will merge back the modifications before the next read. + +Annotating your queries with this information can tel. To annotate that a query writes component `Position` using `ecs_set` looks like this: `[out] :Position`. If you use `get_mut`, you could also read the component, in which case you use `[inout] :Position`. If you use `get`, use `[in] :Position`. In some cases you can't know in advance which components are going to be written. This is for example the case with the `ecs_delete` operation. To annotate a query for this use case, use `[out] :*`. + +## Systems +Designing systems is probably the hardest thing to do when you are not coming from an ECS background. Object oriented code allows you to write logic that is local to a single object, whereas systems in ECS are ran for collections of similar objects. This requires a different approach towards design. + +### System Scope +Try to design your systems with a single responsibility. This can sometimes be difficult, especially if you are building new features and are not exactly sure yet what the end result will look like. That is fine. It is better to start with something that works, and refine it afterwards. If you find yourself with a system that does a lot of things, don't worry. Because systems in ECS are decoupled from everything else, it is generally pretty easy to split them up. + +If you manage to write systems that do only one thing, and do it well, you will have achieved ECS Zen, as you will be able to use that system across lots of projects! + +### System Size +This is somewhat related to the system scope, but the question "when is a system too large" is a bit harder to answer. Sometimes a single feature requires a lot of complexity, such as a damage resolution system in a first person shooter. In these cases it can be more complex to split up the logic between different systems than it is to keep it combined. That is ok. + +If you have a large, complex system the chances if reusing it across different projects is much smaller, but that is not necessarily a problem. A project will always have code that is generic and specific, so don't feel bad if you wrote a complex system that has 500 lines of code. + +### System Scheduling +Flecs has the ability to run and schedule your systems for you. This has a bunch of advantages. You can annotate systems with when they are ran (see phases and pipelines) and this makes it possible to build systems that are plug and play. How does that work? + +A system is basically a combination of three things: a query, a function, and ordering information. The query finds the right entities, and the function is invoked with the matched entities. The ordering information makes sure that the system is inserted in the right point in your frame. If you get this right, you can import any number of systems into your project, and you can just run them without spending any time on manually sorting them out. + +The ordering information consists out of a phase (see phases and pipelines) and an implicit declaration order. Systems are ordered according to their phases first. Within a phase, they are ordered by declaration order. This may feel rigid, but is very deliberate. It prevents you from defining dependencies between systems, which can make it difficult to reuse systems across projects. + +On the other hand, if you are working with an existing framework or engine, you may not have the luxury of scheduling everything yourself. The engine may for example provide you with callbacks in which you need to do certain logic. Maybe you want to build your own threading system. In those situations it can make sense to take control of running systems yourself. Sometimes you may even not use systems at all, and just use queries! + +There is no right or wrong approach here. Different projects require different approaches. Fortunately Flecs makes it really easy to do both! + +## Phases and Pipelines +Phases and pipelines are the primitves that Flecs uses to order systems. A pipeline is a set of ordered phases. Systems can be assigned to those phases. When using phases and pipelines correctly, it allows you to write plug & play systems that are easy to reuse in different projects. + +### Selecting a Phase +When you create a system, you can assign a phase to it. By default, that phase is OnUpdate. Flecs comes with a whole bunch of phases though, and just looking at the whole list can feel a bit overwhelming: + +- OnLoad +- PostLoad +- PreUpdate +- OnUpdate +- OnValidate +- PostUpdate +- PreStore +- OnStore + +So what do these all mean? Actually they mean nothing at all! They are just tags you can assign to systems, and those tags ensure that all systems in, say, the PreUpdate phase are executed _before_ the systems in the _OnUpdate_ phase. What is also important to realize is that this list of phases is only the default provided by Flecs. Maybe your project needs only half of those, or maybe it needs entirely different ones! Flecs lets you specify your custom phases to match your project needs. + +There are some conventions around the builtin phases, and following them helps to ensure that your code works well with the Flecs module ecosystem. Here they are: + +### OnLoad +This phase contains all the systems that load data into your ECS. This would be a good place to load keyboard and mouse inputs. + +### PostLoad +Often the imported data needs to be processed. Maybe you want to associate your keypresses with high level actions rather than comparing explicitly in your game code if the user pressed the 'K' key. The PostLoad phase is a good place for this. + +### PreUpdate +Now that the input is loaded and processed, it's time to get ready to start processing our game logic. Anything that needs to happen after input processing but before processing the game logic can happen here. This can be a good place to prepare the frame, maybe clean up some things from the previous frame, etcetera. + +### OnUpdate +This is usually where the magic happens! This is where you put all of your gameplay systems. By default systems are added to this phase. + +### OnValidate +This phase was introduced to deal with validating the state of the game after processing the gameplay systems. Sometimes you moved entities too close to each other, or the speed of an entity is increased too much. This phase is for righting that wrong. A typical feature to implement in this phase would be collision detection. + +### PostUpdate +When your game logic has been updated, and your validation pass has ran, you may want to apply some corrections. For example, if your collision detection system detected collisions in the OnValidate phase, you may want to move the entities so that they no longer overlap. + +### PreStore +Now that all of the frame data is computed, validated and corrected for, it is time to prepare the frame for rendering. Any systems that need to run before rendering, but after processing the game logic should go here. A good example would be a system that calculates transform matrices from a scene graph. + +### OnStore +This is where it all comes together. Your frame is ready to be rendered, and that is exactly what you would do in this phase. + +That was a quick overview of all the builtin phases. Note that these are just guidelines! Feel free to deviate if your project calls for it. + +### Designing a Pipeline +If you are designing your own pipeline and you feel lost about which phases you should define, start small and expand later. A good pipeline has well defined semantics for each phase, so that you never have to think long about where a system should go. If you want to be really formal about it, create a document that describes how your components are accessed and/or modified in each phase. This will make it much easier to write systems! + +## Modules +Large applications can often contain many components and systems. Some large commercial projects have reported up to 800 components! Managing all those components and systems becomes important on that scale, and that is what modules are for. Modules are one of those features that you usually don't think about when selecting an ECS, but they can make your life a lot easier. + +### Defining Modules +The purpose of modules is really to enable reusability. A well written module can be imported into any project, and will do its thing without any tweaking or tinkering. To achieve this, make sure to define your modules around features. Features seldomly consist out of a single system or component, but can have many. Examples are rendering, collision detection, input management, and so on. + +### Module Dependencies and Ordering +Modules can depend on each other. In fact, often do! Importing a module twice has no penalties in Flecs, it will not define your systems and components twice. This enables your application code to import the modules it needs, without having to worry about whether they were already loaded. + +The order in which you import dependencies in a module is important. If you define systems before the import, those systems will be ran _before_ the systems from the imported module _within the same phase_ (read this line a few times until you understand, it's important). This allows you some degree of flexibility around how systems from different modules should be scheduled. + +What is key to note here is that the granularity of control is at the module level, _never_ at the individual system level. The reason for this is that modules may reimplement their features with different systems. If you have inter-system dependencies, those could break easily every time you update a module. This also makes sure that you can replace one module for another without running into annoying compatibility issues. + +### Modules and Feature Swapping +A good practice to employ with modules is to split them up into components.* modules and systems.* modules. For example, you may have a module `components.physics` that contains all the components to store data for a physics system. You may then have a module caled `systems.physics`, which imports the components module and "implements" the components. Because applications only have access to `components.physics` (they import it as well) but do not have direct access to the systems inside `systems.physics`, you can simply swap one physics implementation with another without changing application code. + +This is a powerful pattern enabled by ECS that will give your projects a lot of flexibility and freedom in refactoring code. + +### Module Overhead +You might wonder whether a module with lots of systems, of which only a few are used by your application makes your application slower. The answer is no. Flecs only inserts systems into the main loop that have matched with actual entities. Any imported systems that have never matched with anything remain dormant, and will not negatively affect your performance. + +## Hierarchies +Hierarchies are one of the most used features of Flecs, and often useful in game development. Here are a few guidelines for using them. + +### Scene graphs +The Flecs hierarchy implementation works quite well with scene graphs. Flecs has out of the box features that let you iterate hierarchies top to bottom (see the `CASCADE` modifier), and this comes in handy when you want to do things like hierarchically applying a transform. Storing a scene graph is a totally fine way to use Flecs hierarchies, and there are nice cross-over benefits from, for example, using prefab hierarchies. + +### Keep 'em clean! +It can sometimes be tempting to use Flecs hierarchies as a fancy container for a list of entities. Don't. Or rather, you _can_, but keep the hierarchy separate from your scene graph hierarchy. Maybe it will work, but tomorrow someone may implement a feature that iterates all entities in the scene graph for some kind of visualization, and you don't want to see thousands of entities in that tree that are just there because it was convenient to use hierarchies. + +To make sure your application can support multiple hierarchies, make sure that you use an explicit root object. This can be your scene, game world, level or whatever you fancy. This way you can always use that scene root entity to get all entities in your scene graph, and not run into unexpected clutter! + +### Don't Sort Manually! +If you have a hierarchy and you want to apply a hierarchical transform (for example), don't sort your entities outside of the ECS. Flecs comes with the CASCADE query modifier (see "Signatures" in the manual) which does this for you. The implementation of CASCADE is such that sorting is only performed when new tables are introduced to Flecs. Without going into details as to what that means: this only happens during the early stage of your application, and hardly ever afterwards. As such it is an extremely efficient way to access your entities in a depth-sorted way! diff --git a/fggl/ecs2/flecs/docs/FAQ.md b/fggl/ecs2/flecs/docs/FAQ.md new file mode 100644 index 0000000000000000000000000000000000000000..4235a1320aa71f0cf8fcf916da15e182767e2e99 --- /dev/null +++ b/fggl/ecs2/flecs/docs/FAQ.md @@ -0,0 +1,214 @@ +# FAQ +Frequently asked questions. + +## General questions + +### Is Flecs fast? +Yes, very. Flecs is implemented using one of the fastest approaches to implement an entity component system (see "What is an archetype"). As a result of this approach, Flecs applications get direct access to component arrays, which enables performance that is close to the theoretical maximum. On top of that Flecs is implemented in C, which allows for a very efficient implementation as the language doesn't get in the way. + +### Can Flecs be used together with existing game engines? +As long as the application is implemented in C or C++, it can use Flecs. Flecs has, for example, been used in projects together with Unreal Engine. + +### Is Flecs a game engine? +No. Flecs provides an efficient way to store your game data and run game logic, but beyond that it does not provide the features you would typically expect in a game engine, such as rendering, input management, physics and so on. + +### Why is Flecs written in C? +One of the main goals of Flecs is portability. Even though new operating systems support the most recent (and future!) versions of the C and C++ language standards, Flecs is used in a number of legacy systems that do not support modern C/C++. Additionally, the C API is easier to embed in existing frameworks and languages as it provides low-level untyped acces to component arrays, and doesn't require wrapping of template-heavy APIs. + +### Should I use the C or C++ API? +It depends. The C++ API is slightly easier to work with as it reduces the amount of boilerplate code significantly. On the other hand, the C API is easier to embed in other frameworks and can more easily be ported to different platforms. + +### Is Flecs compatible with modern C++ standards? +Yes. The Flecs C++ API has been written in C++11 to balance portability with modern C++ features, but applications can utilize modern C++ standards. + +### Which platforms are supported by Flecs? +Flecs is regularly validated on the following platforms: +- Ubuntu Precise, x64 (gcc) +- Ubuntu Xenial, x64 (gcc) +- Ubuntu Xenial, x64 (clang) +- Ubuntu Xenial, arm (gcc) +- Ubuntu Bionic, x64 (gcc) +- Ubuntu Bionic, x64 (clang) +- MacOS 10.14, x64 (clang) +- MacOS 10.10, x64 (clang) +- Windows 10, x64 (msvc, visual studio 2019) + +Additionally, Flecs has been used with emscripten and on iOS. + +### Where can I ask questions about Flecs? +You can ask questions on the [Discord channel](https://discord.gg/trycKxA), or by creating an issue in the repository. + +### What is an archetype? +The term "archetype" in ECS is typically (though not universally) used to describe the approach an ECS framework uses for storing components. Contrary to what the term might suggest, an "archetype" is typically not somtething you would see in the ECS API, but rather the data structure the ECS framework to store components. + +An ECS that implements the archetype storage model stores entities with the same components together in the same set of component arrays (or array, depending on whether the framework uses SoA or AoS). The advantage of this approach is that it guarantees that components are always stored in packed arrays, which allows for code to be more easily vectorized by compilers (or in plain language: code runs faster). + +The Flecs storage model is based on the archetypes approach. + +### What is a table? +The documentation uses the terms archetype and table interchangably. They are the same. Internally in the Flecs source code an archetype is referred to as a table. + +## Development questions + +### Why is my system called multiple times per frame? +Flecs stores entities with the same set of components in the same arrays in an "archetype". This means that entities with `Position` are stored in different arrays than entities with `Position, Velocity`. + +Because Flecs systems provide direct access to C arrays, a system is invoked multiple times, once for each set of arrays. + +### Why is the value of a component not set in an OnAdd trigger? +The OnAdd trigger is invoked before the component value is assigned. If you need to respond to a component value, use an `OnSet` system. For more information on when triggers, monitors and OnSet systems are invoked, see this diagram: https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md#component-add-flow + +### Why does Flecs abort when I try to use threads? +Flecs has an operating system abstraction API with threading functions that are not set by default. Check (or use) the OS API examples to see how to set the OS API. + +### Why am I getting errors like FLECS__TPosition or FLECS_EPosition not found? +Flecs functions need access to component handles before they can do anything. In C++ this is abstracted away behind templates, but in the C API this is the responsibility of the application. See [this section of the manual](https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md#component_handles) on how to pass component handles around in the application. + +### How do I pass component handles around in an application? +See the previous question. + +### When to use each vs. iter when evaluating a query or system? +Queries and systems offer two ways to iterate them, which is using `each` and `iter`. Each is the simpler version of the two, which iterates each individual entity: + +```cpp +world.system<Position, Velocity>() + .each([](flecs::entity e, Position& p, Velocity& v) { + p.x += v.x; + p.y += v.y; + }); +``` + +Iter is more complex, but is faster to evaluate and allows for more control over how the loop is executed: + +```cpp +world.system<Position, Velocity>() + .iter([](flecs::iter& it, Position* p, Velocity* v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); +``` + +Additionally, `iter` can be used for more complex queries (see next question). + +### Why can I create queries and systems both as template parameters and as strings? +In the C++ API you can express simple queries with plain template parameters like this example: + +```cpp +auto q = ecs.query<Position, Velocity>(); + +q.each([](flecs::entity e, Position& p, Velocity& v) { + // ... +}); +``` + +That same query can also be specified as a string, at the cost of a slightly more complex API: + +```cpp +auto q = ecs.query<>("Position, Velocity"); + +q.iter([](flecs::iter& it) { + auto p = it.term<Position>(1); + auto v = it.term<Velocity>(2); +}); +``` + +In most cases using template parameters is the way to go, as this provides a slightly easier to use and nicer API. However, for more complex queries, template parameters are insufficient. For example, queries can select entities in a hierarchy from top to bottom with the `CASCADE` modifier: + +```cpp +auto q = ecs.query<>("CASCADE:Position, Position"); + +q.iter([](flecs::iter& it) { + auto p_parent = it.term<Position>(1); + auto p = it.term<Position>(2); +}); +``` + +So in short, for simple queries, use template parameters. For complex queries, use strings. + +### How do I attach resources to a system? +If you want to attach data to a system, you can specify a `ctx` parameter in the +`ecs_system_desc_t` struct parameter passed to the `ecs_system_init` function: + +```c +ecs_system_init(world, &(ecs_system_init_t){ + .entity = {.name = "MySystem"}, + .callback = MySystemCallback, + .ctx = my_context_ptr +}); +``` + +Alternatively, if you use the ECS_SYSTEM macro you can use `ecs_system_init` +with the handle to the existing system to set the context: + +```c +ECS_SYSTEM(world, MySystem, EcsOnUpdate, Position, Velocity); + +ecs_system_init(world, &(ecs_system_init_t) { + .entity = {MySystem}, + .ctx = my_context_ptr +}) +``` + +This variable will now be accessible through the `ctx` member of the system +iterator: + +```c +void MySystem(ecs_iter_t *it) { + int *my_context_ptr = it->ctx; + // ... +} +``` + +Systems in C++ can use the `ctx` method: + +```cpp +auto sys = world.system<Position, Velocity>() + .ctx(my_context_ptr) + .iter([](flecs::iter& it, Position *p, Velocity *v) { + int *my_context_ptr = it->ctx(); + }); + +sys.ctx(another_ptr); +``` + +### Why is my system (or query) unable to find a component from a module? +When you import a module, components are automatically namespaced to prevent nameclashes between modules. In C this namespace is determined by taking the name of the module and convert it from PascalCase to a dot-separated notation, so that `MyModule` becomes `my.module`. If component `Position` is defined in module `MyModule`, a system or query will need to use `my.module.Position` in the query expression. + +In C++ the component name is prefixed by the C++ namespace, so that `my::module::Position` in C++ becomes `my.module.Position` in the query expression. Note that this only needs to be specified this way when providing a query expression as a string. + +### How do I order my systems? +Systems can be ordered using two mechanisms. The first mechanism is the "phase". By default systems are added to the `EcsOnUpdate` phase, which is usually where all of the gameplay logic is executed. Systems can be assigned to other phases, like `EcsPreUpdate` and `EcsPostUpdate`. For a full list of all phases, see: +https://github.com/SanderMertens/flecs/blob/master/docs/Quickstart.md#pipelines + +The second mechanism is declaration order. If two systems are assigned to the same phase, the order in which they are executed is the order in which they are declared. + +Additionally you can take full control over when to run your systems by not assigning systems to a phase (instead of `EcsOnUpdate` just specify 0). To run a system, you can use the `ecs_run` function: + +```c +// delta_time and some_param may be 0 +ecs_run(world, MySystem, delta_time, &some_param); +``` + +### Are queries order sensitive? +No, a query for `Position, Velocity` matches with the same entities as `Velocity, Position`. + +### Can I add/remove components in a system? +Yes, you can use the regular add/remove functions in systems. Note however that these operations will not have an immediate effect, as they are _deferred_ until the end of the frame. See this diagram on staging for more information: https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md#staging-flow + +### Why are updates to components made by my system lost? +If you have a system in which you both write to component arrays as well as adding/removing components from your entity, it may happen that you lose your updates. A solution to this problem is to split up your system in two, one that sets the component values, and one that adds/removes the components. + +### What is the difference between a system and a query? +They are very similar. A system is a query paired up with a function that is executed automatically by the framework each frame. + +### When should I use queries versus filters? +Queries are the fastest way to iterate over entities as they are "prematched", which means that a query does not need to search as you're iterating it. Queries also provide the most flexible mechanism for selecting entities, with many query operators and other features. Queries are expensive to create however, as they register themselves with other parts of the framework. + +Filters on the other hand are slower to iterate over as they perform searching while you're iterating them, but they are very cheap to create as they are simple stack-objects that require no additional setup. + +Often a combination of queries and filters works best. See the `ecs_query_next_w_filter` function. + +### How do I create tags in an C++ application? +In the C API there is the `ECS_TAG` macro to create tags, but the C++ API does not have an equivalent function or class. The easiest way to create a tag in the C++ API is to create an empty struct, and use it as a regular component. diff --git a/fggl/ecs2/flecs/docs/Manual.md b/fggl/ecs2/flecs/docs/Manual.md new file mode 100644 index 0000000000000000000000000000000000000000..ea85ae5bb23dae5c65aa872de8cc98c8287d7e19 --- /dev/null +++ b/fggl/ecs2/flecs/docs/Manual.md @@ -0,0 +1,2419 @@ +# Flecs Manual + +## Introduction +Nobody likes to read manuals, and you should be able to get up and running with Flecs by using the quickstart, by looking at examples and by checking the documentation in the flecs header files. However, if you truly want to know how something works, or why it works that way, the manual is the right place to go. With that said, the manual is not exhaustive, and it complements the other sources of documentation. + +## Design Goals + +### 1. Performance +Flecs is designed from the ground up to provide blazing fast iteration speeds in systems that can be vectorized by default, while minimizing cache misses. In addition, Flecs has a unique graph-based storage engine that allows for extremely fast add, remove and bulk operations. These features, amongst others, ensure that applications can get the most out of the underlying hardware. + +### 2. Portability +Flecs has been implemented in C99 and features an external interface that is C89 compatible to ensure it is portable to a wide range of platforms. The framework contains a flexible operating system abstraction API that enables an application to easily port the library to new platforms. + +### 3. Reusability +ECS has the potential for being a platform for the development of reusable, loosely coupled, plug and play features like input, physics and rendering. Flecs modules enable such features to be packaged in a loosely coupled way so that applications can simply import them, while guaranteeing a correct execution order. In addition, Flecs has features like time management that ensure a consistent baseline across modules. + +### 4. Usability +Flecs is designed first and foremost to be a framework that simplifies the development of games and simulations. Rather than just providing a vanilla ECS implementation, Flecs provides many features that are commonly found in game development frameworks such as hierarchies, prefabs and time management, all integrated seamlessly with the core ECS system. + +### 5. Extensibility +Flecs is used with other frameworks and game engines, and as such not all of its features are useful in each application. For that reason Flecs has a modular design, so that applications can easily remove features from the core that they do not need. Additionally, since many features are built on top of the ECS core, applications can easily extend or reimplement them. + +### 6. Have fun! +There are few things as satisfying as building games. If nothing else, Flecs has been built to enable creative visions both big and small. I'm having a lot of fun building Flecs, I hope you will have fun using it, and that your users will have fun playing your games :) + +## Concepts +This section describes the high level concepts of Flecs. + +### World +The world is the container in which a simulation lives. It stores all of the simulation data, metadata and systems required to run the simulation. An application can have one or more worlds. + +### Entity +An entity is a unique, 64-bit identifier that represents a single thing or object in your game. Entities have no behavior, and strictly speaking do not even have a lifecycle. Rather, you should see an entity as a numeric "primary key" that is associated with data in the store. In Flecs, the entity type (`ecs_entity_t`) is just that, a 64-bit integer. + +### Component +A component is a datatype that is registered with the world. They can be added to, and removed from an entity. An entity can contain at most a single instance of a component, and can have an infinite number of components (in theory). Flecs internally stores component metadata on an entity, which is why component handles are of type `ecs_entity_t`. + +### Tag +A tag is similar to a component, but is not associated with a data type. Just like components, tags are stored as an entity, which is why tag handles are of type `ecs_entity_t`. + +### Query +A query allows for high performance iteration of entities that match a certain signature. The signature at a minimum contains information about which components an entity needs to have in order to be matched. Signatures can however be much more complex, and can exclude components, optionally match with components, match with components from specific sources and more. + +### System +A system is a function that iterates over entities that, just like queries, match a signature. Systems can either be standalone or part of a _pipeline_, in which case they are executed periodically as part of running the main loop. Flecs internally stores system metadata on an entity, which is why system handles are of type `ecs_entity_t`. + +### Phase +A phase identifies a moment in a frame when a system should be executed. A system can only be associated with a single phase. A phase is implemented as a tag, which is why phase handles are of type `ecs_entity_t`. A system can be made part of a phase by adding the phase to the system entity. + +### Pipeline +A pipeline contains all the phases that should be executed by the world when a frame is computed. Flecs has a builtin pipeline which contains builtin phase tags, but applications can define their own custom pipelines. Flecs stores pipelin metadata on an entity, which is why pipeline handles are of type `ecs_entity_t`. + +### Monitor +A monitor is a special kind of system that is executed when an entity goes from not matching the signature to matching the signature. Monitors can be used to monitor whether an entity has a specific set of entities. + +### Set systems +Set systems are a special kind of system that are executed when one or more components are set or unset. Set systems are a reliable way of getting notified when the value of a component changes. + +### Trigger +A trigger is a function that is executed when a component is added or removed. Triggers are simpler than systems, and can only be registered for a single component. Flecs stores triggers internally as an entity which is why trigger handles are of type `ecs_entity_t`. + +### Type +A type is a collection (vector) of entity identifiers (`ecs_entity_t`) that can describe anything from a set of components, systems and tags, to plain entities. Every entity is associated with exactly one type that describes which components, tags, or other it has. + +### Type role +A type role is a flag that indicates which role an entity fulfills in a type. By default this flag is `0`, indicating that the entity should be interpreted as component or tag. Type roles are most commonly used in applications for switch types (`ECS_SWITCH` and `ECS_CASE`) + +### Scope +A scope is a virtual container that contains all of the child entities for a specific parent entity. Scopes are not recursive. Scopes are identified by their parent, which is why scope handles are of type `ecs_entity_t`. + +### Namespace +Namespace is a different word for scope, and the terms may be used interchangeably. Typically the term namespace is used in relationship to components, systems and modules, whereas the term scope is used in relation to regular entities. + +### Module +A module groups components, systems and more into reusable units that can be imported by an application. Contents of a module are by default stored in the scope (or namespace) of the module. Modules can import other modules. A module is only loaded once, regardless of how many times an application imports it. + +### Stage +A stage is a temporary storage where structural changes to entities (new, add, remove, delete) are stored while an application is iterating. Flecs systems access raw C arrays, and this ensures that these arrays are not modified as the application is iterating over them. Data in a stage is merged at least once per frame, or more when necessary. + +## Diagrams + +### High level architecture +This diagram provides an overview of how entiies, components, tables, queries, filters and systems are wired together. + + +### Component add flow +This diagram provides an overview of the different steps that occur when adding a component to an entity. The diagram shows when component lifecycle callbacks, OnAdd triggers, OnSet systems, UnSet systems and monitors are invoked. Additionally the diagram shows how the defer mechanism is integrated with the listed Flecs operations. + + +### Component remove flow +This diagram provides an overview of the different steps that occur when removing a component from an entity. The diagram shows when component lifecycle callbacks, OnRemove triggers, OnSet systems, UnSet systems and monitors are invoked. Additionally the diagram shows how the defer mechanism is integrated with the listed Flecs operations. + + +### Staging flow +This diagram provides an overview of what happens when an application uses staging. Staging is a lockless mechanism that lets threads concurrently read & perform structural changes on the store. Changes are temporarily stored in a command queue per stage, which can be merged with the store when convenient. + + +## Building +The easiest way to add Flecs to a project is to add [flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.c) and [flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.h) to your source code. These files can be added to both C and C++ projects (the C++ API is embedded in flecs.h). Alternatively you can also build Flecs as a library by using the cmake, meson, bazel or bake buildfiles. + +### Custom builds +Whether you're looking for a minimal ECS library or a full-fledged system runtime, customizable builds let you remove Flecs features you don't need. By default all features are included. To customize a build, follow these steps: + +- define `FLECS_CUSTOM_BUILD`. This removes all optional features from the build. +- define constants for the features you want to include (see below) +- remove the files of the features you don't need + +Features are split up in addons and modules. Addons implement a specific Flecs feature, like snapshots. Modules are like addons but register their own components and systems, and therefore need to be imported. + +#### Addons +Addons are located in the `src/addons` and `include/addons` folders. The following addons are available: + +Addon | Description | Constant | +--------------|--------------------------------------------------|---------------------| +Bulk | Efficient operations that run on many entities | FLECS_BULK | +Dbg | Debug API for inspection of internals | FLECS_DBG | +Stats | Collect statistics on entities and systems | FLECS_STATS | +Direct Access | Low-level API for direct access to component data| FLECS_DIRECT_ACCESS | +Module | Organize components and systems in modules | FLECS_MODULE | +Queue | A queue data structure | FLECS_QUEUE | +Snapshot | Take a snapshot that can be restored afterwards | FLECS_SNAPSHOT | + +#### Builtin modules +Modules are located in the `src/modules` and `include/modules` folders. The following modules are available: + +Module | Description | Constant | +--------------|--------------------------------------------------|---------------------| +System | Support for systems, monitors and triggers | FLECS_SYSTEM | +Pipeline | Run systems each frame and/or multithreaded | FLECS_PIPELINE | +Timer | Run systems at intervals, timeouts or fixed rate | FLECS_TIMER | + +## API design + +### Naming conventions + +```c +// Component names ('Position') use PascalCase +typedef struct Position { + float x; + float y; // Component members ('y') use snake_case +} Position; + +typedef struct Velocity { + float x; + float y; +} Velocity; + +// System names ('Move') use PascalCase. API types use snake_case_t +void Move(ecs_iter_t *it) { + // Functions use snake_case + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init(); + + // Declarative function-style macro's use SCREAMING_SNAKE_CASE + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + // Module names are PascalCase + ECS_IMPORT(world, MyModule); + + // Enumeration constants ('EcsOnUpdate') use PascalCase + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + // Function wrapper macro's use snake_case + ecs_entity_t e = ecs_new(world, 0); + + // Builtin entities use PascalCase + ecs_add(world, EcsWorld, Position); + + return ecs_fini(world); +} +``` + +### Idempotence +Many operations in the Flecs API are idempotent, meaning that invoking an operation once has the same effect as invoking an operation multiple times with the same parameters. For example: + +```c +ecs_add(world, e, Position); +``` + +Has the same effect as: + +```c +ecs_add(world, e, Position); +ecs_add(world, e, Position); +``` + +This simplifies application code as it can be written in a declarative style, where the only thing that matters is that after the operation has been invoked, the post condition of the operation is satisfied. + +Some operations are idempotent but have side effects, like `ecs_set`: + +```c +ecs_set(world, e, Position, {10, 20}); +ecs_set(world, e, Position, {10, 20}); +``` + +The effect of invoking this operation once is the same as invoking the operation multiple times, but both invocations can trigger an OnSet system which can introduce side effects. + +All declarative macro's (`ECS_COMPONEN`, `ECS_SYSTEM`, ...) are idempotent: + +```c +{ + ECS_COMPONENT(world, Position); +} +{ + ECS_COMPONENT(world, Position); +} +``` + +The second time the `ECS_COMPONENT` macro is evaluated, the first instance will be found and returned. Note that because these macro's may declare variables, they cannot be defined twice in the same C scope. + +### Error handling +As a result of the idempotent design of many operations, the API has a very small error surface. There are essentially two conditions under which an operation is unable to fulfill its postcondition: + +- The application provides invalid inputs to an operation +- The operating system is unable to fulfill a request, like a failure to allocate memory + +When either of those conditions occur, the library will throw an assertion in debug mode (the source is not compiled with `NDEBUG`). Except for errors caused by the OS, errors are almost always caused by the invocation of a single operation, which makes applications easy to debug. + +This approach has several advantages. Application code does not need to check for errors. If an error occurs, the assertion will cause application execution to halt. As a result of this, application code is cleaner and more robust, as it is impossible to forget to handle an error condition. + +### Memory ownership +Most of the API is handle based, as many API constructs are implemented using entities. There are a few instances where an application will interface with memory managed by the framework, or when an application needs to provide memory it manages to the API. In these scenarios there are four rules: + +- If an operation accepts a `const T*`, the application retains ownership of the memory +- If an operation accepts a `T*`, ownership is transferred from application to framework +- If an operation returns a `const T*`, the framework retains ownership of the memory +- If an operation returns a `T*`, ownership is transferred from framework to application + +The `ecs_get_name` operation is an example where the framework retains ownership: + +```c +const char *name = ecs_get_name(world, e); +``` + +The `ecs_get_fullpath` operation is an example where the ownership is transferred to the application: + +```c +char *path = ecs_get_fullpath(world, e); +``` + +Memory for which ownership has been transferred to the application will need to be freed by the application. This should be done by the `ecs_os_free` operation: + +```c +ecs_os_free(path); +``` + +### Entity names +An application can assign names to entities. Names can be assigned at entity creation, with the `ecs_entity_init` function: + +```c +ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = "MyEntity" +}); +``` + +Alternatively, names can be assigned afterwards with the `ecs_set_name` function: + +```c +ecs_set_name(world, e, "MyEntity"); +``` + +The `ecs_set_name` function may be used as a shortcut to create a new named entity by providing 0 for the entity argument: + +```c +ecs_entity_t e = ecs_set_name(world, 0, "MyEntity"); +``` + +The name of an entity can be retrieved with the `ecs_get_name` function: + +```c +printf("Name = %s\n", ecs_get_name(world, e)); +``` + +The entity name is stored in the `EcsName` component, which can be retrieved like any component with `ecs_get`: + +```c +const EcsName *name = ecs_get(world, e, EcsName); +printf("Name = %s\n", name->value); +``` + +Names can be used to lookup entities: + +```c +ecs_entity_t e = ecs_lookup(world, "MyEntity"); +``` + +When an entity is part of a hierarchy, names can be used to form a path: + +```c +ecs_entity_t parent = ecs_new_id(world); +ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); +ecs_entity_t grandchild = ecs_new_w_pair(world, EcsChildOf, child); + +ecs_set_name(world, parent, "Parent"); +ecs_set_name(world, child, "Child"); +ecs_set_name(world, grandchild, "GrandChild"); + +char *path = ecs_get_fullpath(world, grandchild); +printf("Path = %s\n", path); // prints Parent.Child.GrandChild +ecs_os_free(path); +``` + +A path can be created relative to a parent: + +```c +char *path = ecs_get_path(world, parent, grandchild); +printf("Path = %s\n", path); // prints Child.GrandChild +ecs_os_free(path); +``` + +Paths can be used to lookup an entity: + +```c +ecs_entity_t e = ecs_lookup_fullpath(world, "Parent.Child.GrandChild"); +``` + +Path lookups may be relative: + +```c +ecs_entity_t e = ecs_lookup_path(world, parent, "Child.GrandChild"); +``` + +### Macro's +The C99 API heavily relies on function-style macro's, probably more than you would see in other libraries. The number one reason for this is that an ECS framework needs to work with user-defined types, and C does not provide out of the box support for generics. A few strategies have been employed in the API to improve its overall ergonomics, type safety and readability. Let's start with a simple example: + +```c +typedef struct Position { + float x; + float y; +} Position; + +ECS_COMPONENT(world, Position); + +ecs_entity_t e = ecs_new(world, Position); +``` + +From a readability perspective this code looks fine as we can easily tell what is happening here. Though if we take a closer look, we can see that a typename is used where we expect an expression, and that is not possible in plain C. So what is going on? + +Let's first remove the `ECS_COMPONENT` macro and replace it with equivalent code (details are omitted for brevity): + +```c +ecs_entity_t ecs_id(Position) = ecs_component_init(world, &(ecs_component_desc_t){ + .entity.name = "Position", + .size = sizeof(Position), + .alignment = ECS_ALIGNOF(Position) +}); +ecs_type_t ecs_type(Position) = ecs_type_from_id(world, ecs_id(Position)); +``` + +The first line actually registers the component with Flecs, and captures its name and size. The result is stored in a variable with name `ecs_id(Position)`. Here, `ecs_entity` is a macro that translates the typename of the component to a variable name. The actual name of the variable is: + +```c +FLECS__EPosition +``` + +The second thing that happens is that a type variable is declared with the name `ecs_type(Position)`, which translates to `FLECS__TPosition`. A type is a vector of components. In this case, the type only contains the component id for `Position`. We will see in a moment why this is necessary. + +The next statement creates a new entity with the `Position` component. The `ecs_new` function is a macro in disguise, and when it is replaced with the actual code, it looks like this: + +```c +ecs_entity_t e = ecs_new_w_type(world, ecs_type(Position)); +``` + +We can see that the actual plain C function that is called is `ecs_new_w_type`, and that the macro is passing in the type variable into the function. When creating a new entity, it can be initialized with multiple components which is why it accepts a type. Other operations, like `ecs_get` only accept a single component, and use the entity variable: + +```c +Position *p = ecs_get(world, e, Position); +``` + +Translates into: + +```c +Position *p = (Position*)ecs_get_id(world, e, ecs_id(Position)); +``` + +As you can see, the `ecs_get` macro casts the result of the function to the correct type, so a compiler will throw a warning when an application tries to assign the result of the operation to a variable of the wrong type. + +Similarly, `ecs_set` is a macro that ensures that anything we pass into it is of the right type: + +```c +ecs_set(world, e, Position, {10, 20}); +``` + +Translates into: + +```c +ecs_set_id + (world, e, ecs_id(Position), sizeof(Position), + &(Position){10, 20}); +``` + +In addition to casting the value to the right type and passing in the component, this macro also captures the size of the type, which saves Flecs from having to do a component data lookup. + +Understanding how the macro's work will go a long way in being able to write effective code in Flecs, and will lead to less surprises when debugging the code. + +## Good practices +Writing code for an Entity Component System is different from object oriented code, and while the concepts are not very complex, it can be counter intuitive if you have never used one. This section provides a couple of guidelines on how to write code for ECS and Flecs specifically to get you started. + +### ECS guidelines +The following guidelines apply to ECS in general. + +- When building a new feature, start with the components. Spend time thinking about how the components will be used: how often are they written/read, who owns them, how many instances etc. + +- Design components so that they have a single purpose. It is much easier / cheaper to combine two components in a query than it is to split up components, which causes a lot of refactoring. + +- Don't over-generalize. If your code has lots of branches it is possible that you're trying to do too much with a single component. Consider splitting up components. It's ok to have multiple components with the same fields, if this makes your business logic simpler. + +- Minimize the number of branches (`if`, `while`, `for`) in a system. Branches are expensive and prevent vectorization of code. + +- Think twice before adding collections to components. Collections are OK if the contents of the collection are meant to be interpreted as an atomic unit, but if the individual elements must be interpreted in a standalone way, consider creating separate entities instead. + +- Avoid dependencies / direct interaction between systems. Components are the only things that should be used for inter-system communication. + +- Don't store function pointers in components. If you need to provide different (mutually exclusive) implementations for a component, use systems & tags. + +- Don't feel like you should store everything in ECS. ECS is great for iterating large sets of entities linearly, but things more specialized than that (like spatial data structures) are better implemented separately. You can always cross-reference entity handles from your specialized data structure. + +### Performance guidelines +When starting with ECS, it is important to build intuition around how expensive different operations are in ECS. Here are a few general observations. Note that these may vary across ECS implementations: + +- Iteration over large sets of similar entities is super fast + +- Random access (`ecs_get`) is comparatively slow + +- Entity creation / destruction is cheap when compared with OOP + +- Adding/removing components is comparable to creation/deletion + +- Creating systems and queries is slow, evaluating systems and queries is very fast + +- Creating a filter is fast, evaluating a filter is slow when compared to a query + +### Flecs guidelines +The following guidelines apply to Flecs specifically. Following these guidelines will ensure that your applications perform well, are well organized and that their systems can be reused in multiple projects. + +- Write periodic logic in systems. Systems provide a unified mechanism for running logic in the main loop and make features like time management available to the application. Writing logic in systems makes it easy to organize features in composable units. + +- Always use `delta_time` in simulation systems to progress component values. Aside from making sure the execution of logic is decoupled from the number of frames per second, an application may want to pause or slow down the simulation, which is only possible if all simulation systems consistently use `delta_time`. + +- Use POD (plain old data) components wherever possible. + +- Use component lifecycle actions for managing memory owned by a component. + +- Preallocate memory where possible with `ecs_dim` and `ecs_dim_type`, as this makes application performance more predictable. + +- Decide what your pipeline looks like. A pipeline defines the phases your main loop will go through, and determines where your systems should run. You can use the Flecs builtin pipeline, which enables you to use the flecs module ecosystem, or you can define your own. + +- Understand how different components are accessed in each phase of the pipeline. This will help you determine how to assign systems to different pipelines. + +- Annotate system signatures with `[in]` and `[out]` where possible. This will not only make it easier for someone to reason about dataflow in an application, but will also allow Flecs to optimize system execution. The default is `[inout]`. + +- Organize high level features into modules. Features are often implemented with multiple systems and components, and keeping them together in a module makes it easier to import features, while keeping code organized in large applications. + +- Put components that are not specific to any feature in particular (for example: `Position`) in a their own module. Component-only modules provide the bedrock for an ecosystem where different modules can coexist while working on the same data. + +- Build declarative, component-driven APIs where possible. Flecs allows applications to define behavior when a component value changes. Using components instead of normal functions enables external tools to introspect and configure your applications, especially when used in combination with modules like `flecs.meta` and `flecs.dash`. + +- Use `ecs_progress` to run the main loop. This will make it possible to plug & play new features into application by importing modules from the Flecs ecosystem, and also enable applications to use automatic FPS control, time management and multithreading. + +- Use component references (`ecs_get_ref`) when repeatedly accessing the same component. This is a faster alternative to `ecs_get`, with as only downside that a little bit of state has to be stored. + +## Entities +Entities are uniquely identifiable objects in a game or simulation. In a real time strategy game, there may be entities for the different units, buildings, UI elements and particle effects, but also for exmaple the camera, world and player. An entity does not contain any state, and is not of a particular type. In a traditional OOP-based game, you may expect a tank in the game is of class "Tank". In ECS, an entity is simply a unique identifier, and any data and behavior associated with that entity is implemented with components and systems. + +In Flecs, an entity is represented by a 64 bit integer, which is also how it is exposed on the API: + +```c +typedef uint64_t ecs_entity_t; +``` + +Zero indicates an invalid entity. Applications can create new entities with the `ecs_new` operation: + +```c +ecs_entity_t e = ecs_new(world, 0); +``` + +This operation guarantees to return an unused entity identifier. The first entity returned is not 1, as Flecs creates a number of builtin entities during the intialization of the world. The identifier of the first returned entity is stored in the `EcsFirstUserEntityId` constant. + +### Id recycling +Entity identifiers are reused when deleted. The `ecs_new` operation will first attempt to recycle a deleted identifier before producing a new one. If no identifier can be recycled, it will return the last issued identifier + 1. + +Entity identifiers can only be recycled if they have been deleted with `ecs_delete`. When `ecs_delete` is invoked, the generation count of the entity is increased. The generation is encoded in the entity identifier, which means that any existing entity identifiers with the old generation encoded in it will be considered not alive. Calling a delete multiple times on an entity that is not alive has no effect. + +When using multiple threads, the `ecs_new` operation guarantees that the returned identifiers are unique, by using atomic increments instead of a simple increment operation. New ids generated from a thread will not be recycled ids, since this would require taking a lock on the administration. While this does not represent a memory leak, it could cause ids to rise over time. If this happens and is an issue, an application should precreate the ids. + +### Generations +When an entity is deleted, the generation count for that entity id is increased. The entity generation count enables an application to test whether an entity is still alive or whether it has been deleted, even after the id has been recycled. Consider: + +```c +ecs_entity_t e = ecs_new(world, 0); +ecs_delete(world, e); // Increases generation + +e = ecs_new(world, 0); // Recycles id, but with new generation +``` + +The generation is encoded in the entity id, which means that even though the base id is the same in the above example, the value returned by the second `ecs_new` is different than the first. + +To test whether an entity is alive, an application can use the `ecs_is_alive` call: + +```c +ecs_entity_t e1 = ecs_new(world, 0); +ecs_delete(world, e1); + +ecs_entity_t e2 = ecs_new(world, 0); +ecs_is_alive(world, e1); // false +ecs_is_alive(world, e2); // true +``` + +It is not allowed to invoke operations on an entity that is not alive, and doing so may result in an assert. The only operation that is allowed on an entity that is not alive is `ecs_delete`. Calling delete multiple times on an entity that is not alive will not increase the generation. Additionally, it is also not allowed to add child entities to an entity that is not alive. This will also result in an assert. + +There are 16 bits reserved for generation in the entity id, which means that an application can delete the same id 65536 times before the generation resets to 0. To get the current generation of an entity, applications can use the `ECS_GENERATION` macro. To extract the entity id without the generation, an application can apply the `ECS_ENTITY_MASK` with a bitwise and: + +```c +ecs_entity_t generation = ECS_GENERATION(e); +ecs_entity_t id = e & ECS_ENTITY_MASK; +``` + +### Manual id generation +Applications do not have to rely on `ecs_new` and `ecs_delete` to create and delete entity identifiers. Entity ids may be used directly, like in this example: + +```c +ecs_add(world, 42, Position); +``` + +This is particularly useful when the lifecycle of an entity is managed by another data source (like a multiplayer server) and prevents networking code from having to check whether the entity exists. This also allows applications to reuse existing identifiers, as long as these fit inside a 64 bit integer. + +When not using manual ids, id recycling mechanisms are bypassed as these are only invoked by the `ecs_new` and `ecs_delete` operations. Combining manual ids with `ecs_new` and `ecs_delete` can result in unexpected behavior, as `ecs_new` may return an identifier that an application has already used. + +### Id ranges +An application can instruct Flecs to issue ids from a specific offset and up to a certain limit with the `ecs_set_entity_range` operation. This example ensures that id generation starts from id 5000: + +```c +ecs_set_entity_range(world, 5000, 0); +``` + +If the last issued id was higher than 5000, the operation will not cause the last id to be reset to 5000. An application can also specify the highest id that can be generated: + +```c +ecs_set_entity_range(world, 5000, 10000); +``` + +If invoking `ecs_new` would result in an id higher than `10000`, the application would assert. If `0` is provided for the maximum id, no uppper bound will be enforced. + +It is possible for an application to enforce that entity operations (`ecs_add`, `ecs_remove`, `ecs_delete`) are only allowed for the configured range with the `ecs_enable_range_check` operation: + +```c +ecs_enable_range_check(world, true); +``` + +This can be useful for enforcing that an application is not modifying entities that are owned by another datasource. + + +## Types + +### Basic usage +A type is typically used to describe the contents (components) of an entity. A simple example: + +```c +// Create entity with type Position +ecs_entity_t e = ecs_new(world, Position); + +// Add Velocity to the entity +ecs_add(world, e, Velocity); +``` + +After running this code, the type can be printed: + +```c +// Print the type of the entity +ecs_type_t type = ecs_get_type(world, e); +char *str = ecs_type_str(world, type); +``` + +Which will produce: + +``` +Position, Velocity +``` + +Types can be used to add multiple components in one operation: + +```c +ecs_entity_t e2 = ecs_new_w_type(world, type); +``` + +Alternatively, the `ECS_TYPE` macro can be used to create a type: + +```c +ECS_TYPE(world, MyType, Position, Velocity); + +ecs_entity_t e = ecs_new(world, MyType); +``` + +### Advanced usage +A type is stored as a vector of identifiers. Because components are stored as entities in Flecs, a type is defined as (pseudo, not actual definition): + +```cpp +typedef vector<ecs_entity_t> ecs_type_t; +``` + +As a result, an application is able to do this: + +```c +ecs_entity_t tag_1 = ecs_new(world, 0); +ecs_entity_t tag_2 = ecs_new(world, 0); + +ecs_entity_t e = ecs_new(world, 0); +ecs_add_id(world, e, tag_1); +ecs_add_id(world, e, tag_2); +``` + +Printing the contents of the type of `e` now would produce something similar to: + +``` +256, 257 +``` + +When the type contained components the names of the components were printed. This is because the component entities contained an `EcsName` component. The following example sets the names for `tag_1` and `tag_2`: + +```c +ecs_set_name(world, tag_1, "tag_1"); +ecs_set_name(world, tag_2, "tag_2"); +``` + +Printing the type again will now produce: + +``` +tag_1, tag_2 +``` + +### Type roles +Type roles are flags that can be added to an identifier which provide information to how components should behave and be stored. A feature that uses type roles is switch types: + +```c +ECS_TAG(world, Running); +ECS_TAG(world, Walking); +ECS_TYPE(world, Movable, Running, Walking); + +ecs_entity_t entity = ecs_new_w_id(world, ECS_SWITCH | Movable); +``` + +Here, `ECS_SWITCH` is the type role. This is an overview of the different roles: + +| Flag | Description | +|------|-------------| +| ECS_SWITCH | The entity is a switch type | +| ECS_CASE | The entity is a case belonging to a switch type | +| ECS_OWNED | The entity is a component for which ownership is enforced | + +Entities with type roles can be dynamically added or removed: + +```c +ecs_add_id(world, entity, ECS_SWITCH | Movable); +ecs_remove_id(world, entity, ECS_SWITCH | Movable); +``` + +Additionally, type roles can also be used inside of type and signature expressions, such as in the `ECS_TYPE` and `ECS_ENTITY` macro's: + +```c +ECS_TAG(world, Running); +ECS_TAG(world, Walking); +ECS_TYPE(world, Movement, Running, Walking); +ECS_ENTITY(world, Parent, Position, SWITCH | Movement, CASE | Running); +``` + +Note that when used inside a type expression, there is no need to provide the `ECS` prefix. + +### Type constraints +Type constraints are special type roles that allow an application to put constraints on what entities a type can contain. Type constraints apply to type +entities, typically created with the `ECS_TYPE` macro. An example: + +```c +// Sandwich toppings +ECS_TAG(world, Bacon); +ECS_TAG(world, Lettuce); +ECS_TAG(world, Tomato); + +// A type that contains all sandwich toppings +ECS_TYPE(world, Toppings, Bacon, Lettuce, Tomato); + +// Create a sandwich entity, enforce it has at least one topping +ECS_ENTITY(world, Sandwich, Bacon, Lettuce, OR | Toppings); +``` + +The `Sandwich` entity contains an `OR` type constraint that is applied to the `Toppings` type. This enforces that the entity must have _at least one_ of the entities in the `Toppings` type in its type. An overview of the constraints: + +| Constraint | Description | +|------|-------------| +| ECS_AND | Entity must have all entities from provided type | +| ECS_OR | Entity must have at least one entity from provided type | +| ECS_NOT | Entity must have no entities from provided type | +| ECS_XOR | Entity must have exactly one entity from provided type | + +Type constraints can be added and removed like other type roles: + +```c +ecs_add_id(world, child, ECS_OR | Toppings); +ecs_remove_id(world, child, ECS_OR | Toppings); +``` + +## Components +A component is a plain datatype that can be attached to an entity. An entity can contain any number of components, and each component can be added only once per entity. Components are registered with a world using the `ECS_COMPONENT` macro, after which they can be added and removed to and from entities. Components can be of any datatype. The following example shows how to register and use components: + +```c +// Components can be defined from regular types +typedef struct Position { + float x, y; +} Position; + +int main() { + ecs_world_t *world = ecs_init(); + + // Register the component with the world + ECS_COMPONENT(world, Position); + + // Create a new entity with the component + ecs_entity_t e = ecs_new(world, Position); + + // Remove the component from the entity + ecs_remove(world, e, Position); + + // Add the component again + ecs_add(world, e, Position); +} +``` + +Component values can be set with the `ecs_set` operation. If the entity did not yet have the component, it will be added: + +```c +ecs_set(world, e, Position, {10, 20}); +``` + +Applications can get the value of a component with the `ecs_get` function: + +The value of a component can be requested with `ecs_get`, which will return `NULL` if the entity does not have the component: + +```c +const Position *p = ecs_get(world, e, Position); +``` + +The `ecs_get` operation returns a const pointer which should not be modified by the application. An application can obtain a mutable pointer with `ecs_get_mut`. The `ecs_get_mut` operation ensures that, even when using multiple threads, an application obtains a pointer to a component that can be safely modified, whereas the `ecs_get` operation might return a pointer to memory that is shared between threads. When an application modified a component obtained with `ecs_get_mut`, it should invoke `ecs_modified` to let the framework know the component value was changed. An example: + +```c +Position *p = ecs_get_mut(world, e, Position); +p->x ++; +ecs_modified(world, p, Position); +``` + +### Component handles +In order to be able to add, remove and set components on an entity, the API needs access to the component handle. A component handle uniquely identifies a component and is passed to API functions. There are two types of handles that are accepted by API functions, a type handle and an entity handle. These handles are automatically defined as variables by the `ECS_COMPONENT` macro. If an application wants to use the component in another scope, the handle will have to be either declared globally or passed to that scope explicitly. + +#### Global component handles +To globally declare a component, an application can use the `ECS_COMPONENT_DECLARE` and `ECS_COMPONENT_DEFINE` macro's: + +```c +// Declare component variable in the global scope +ECS_COMPONENT_DECLARE(Position); + +// Function that uses the global component variable +ecs_entity_t create_entity(ecs_world_t *world) { + return ecs_new(world, Position); +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init(); + + // Register component, assign id to the global component variable + ECS_COMPONENT_DEFINE(world, Position); + + ecs_entity_t e = create_entity(world); + + return ecs_fini(world); +} +``` + +To make a component available for other source files, an application can use the `ECS_COMPONENT_EXTERN` macro in a header: + +```c +ECS_COMPONENT_EXTERN(Position); +``` + +Declaring components globally works with multiple worlds, as the second time a component is registered it will use the same id. There is one caveat: an application should not define a component in world 2 that is not defined in world 1 _before_ defining the shared components. The reason for this is that if world 2 does not know that the shared component exists, it may assign its id to another component, which can cause a conflict. + +If this is something you cannot guarantee in an application, a better (though more verbose) way is to use local component handles. + +#### Local component handles +When an application cannot declare component handles globally, it can pass component handles manually. Manually passing component handles takes the variables that are declared by the `ECS_COMPONENT` macro and passes them to other functions. This section describes how to pass those handles around. + +Some operations can process multiple components in a single operation, like `ecs_add` and `ecs_remove`. Such operations require a handle of `ecs_type_t`. The `ECS_COMPONENT` macro defines a variable of `ecs_type_t` that contains only the id of the component. The variable defined by `ECS_COMPONENT` can be accessed with `ecs_type(ComponentName)`. This escapes the component name, which is necessary as it would otherwise conflict with the C type name. The following example shows how to pass a type handle to another function: + +```c +typedef struct Position { + float x, y; +} Position; + +void new_w_position(ecs_world_t *t, ecs_type_t ecs_type(Position)) { + // ecs_new uses an ecs_type_t + ecs_new(world, Position); +} + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + new_w_position(world, ecs_type(Position)); + + ecs_fini(world); +} +``` + +The `ecs_new`, `ecs_add` and `ecs_remove` (not exhaustive) functions are wrapper macro's arround functions functions that accept a type. The following code is equivalent to the previous example: + +```c +typedef struct Position { + float x, y; +} Position; + +void new_w_position(ecs_world_t *t, ecs_type_t p_handle) { + // Use plain variable name with the ecs_new_w_type operation + ecs_new_w_type(world, p_handle); +} + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + new_w_position(world, ecs_type(Position)); + + ecs_fini(world); +} +``` + +There are also operations which operate on a single component at a time, like `ecs_get` and `ecs_set`. These operations require a component handle of type `ecs_entity_t`. The `ECS_COMPONENT` macro defines a variable of type `ecs_entity_t`that contains the id of the component. The variable defined by `ECS_COMPONENT` can be accessed by the application with `ecs_id(ComponentName)`. The following example shows how to pass an entity handle to another function: + +```c +typedef struct Position { + float x, y; +} Position; + +void set_position(ecs_world_t *t, ecs_entity_t ecs_id(Position)) { + ecs_entity_t e = ecs_new(world, 0); + ecs_set(world, e, Position, {10, 20}); +} + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + set_position(world, ecs_id(Position)); + + ecs_fini(world); +} +``` + +The `ecs_set`, `ecs_get` (not exhaustive) functions are wrapper macro's arround functions functions that accept a type. The following code shows how to use the underlying function for `ecs_get`, `ecs_get_id`: + +```c +typedef struct Position { + float x, y; +} Position; + +const Position* get_position(ecs_world_t *t, ecs_entity_t e, ecs_entity_t p_handle) { + return ecs_get_id(world, e, p_handle); +} + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + Position *p = get_position(world, e, ecs_id(Position)); + + ecs_fini(world); +} +``` + +### Component disabling +Components can be disabled, which prevents them from being matched with queries. Contrary to removing a component, disabling a component does not remove it from an entity. When a component is enabled after disabling it, the original value of the component is restored. + +To enable or disable a component, use the `ecs_enable_component` function: + +```c +typedef struct Position { + float x, y; +} Position; + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + /* Component is enabled by default */ + + /* Disable the component */ + ecs_enable_component(world, e, Position, false); + + /* Will return false */ + printf("%d\n", ecs_is_component_enabled(world, e, Position)); + + /* Re-enable the component */ + ecs_enable_component(world, e, Position, true); + + ecs_fini(world); +} +``` + +Component disabling works by maintaining a bitset alongside the component array. When a component is enabled or disabled, the bit that corresponds with the entity is set to 1 or 0. Bitsets are not created by default. Only after invoking the `ecs_enable_component` operation for an entity will be entity be moved to a table that keeps track of a bitset for that component. + +When a query is matched with a table that has a bitset for a component, it will automatically use the bitset to skip disabled values. If an entity contains multiple components tracked by a bitset, the query will evaluate each bitset and only yield entities for which all components are enabled. To ensure optimal performance, the query will always return the largest range of enabled components. Nonetheless, iterating a table with a bitset is slower than a regular table. + +If a query is matched with a table that has one or more bitsets, but the query does not match with components tracked by a bitset, there is no performance penalty. + +Component disabling can be used to temporarily suspend and resume a component value. It can also be used as a faster alternative to `ecs_add`/`ecs_remove`. Since the operation only needs to set a bit, it is a significantly faster alternative to adding/removing components, at the cost of a slightly slower iteration speed. If a component needs to be added or removed frequently, enabling/disabling is recommended. + +#### Limitations +Component disabling does not work for components not matched with the entity. If a query matches with a component from a base (prefab) or parent entity and the component is disabled for that entity, the query will not take this into account. If entities with disabled components from a base or parent entity need to be skipped. a query should manually check this. + +Because component disabling is implemented with a type role, it cannot be used together with other type roles. This means that it is not possible to disable, for example, tags with `SWITCH` or `CASE` roles. Additionally since relationships rely on a role, it is currently not possible to disable relationships such as `(ChildOf, parent)` or `(IsA, prefab)`. + +Another limitation is that currently the query NOT (!) operator does not take into account disabled entities. The optional operator (?) technically works, but a query is unable to see whether a component has been set or not as both the enabled and disabled values are returned to the application in a single array. + +## Tagging +Tags are much like components, but they are not associated with a data type. Tags are typically used to add a flag to an entity, for example to indicate that an entity is an Enemy: + +```c +int main() { + ecs_world_t *world = ecs_init(); + + // Register the tag with the world. There is no Enemy type + ECS_TAG(world, Enemy); + + // Add the Enemy tag + ecs_add(world, e, Enemy); + + // Remove the Enemy tag + ecs_remove(world, e, Enemy); +} +``` + +### Tag handles +Just like components, the API needs a handle to a tag before it can use it, and just like `ECS_COMPONENT`, the `ECS_TAG` macro defines two variables, one of type `ecs_type_t` and one of `ecs_entity_t`. Passing a handle of an `ecs_type_t` into a function looks similar to a component: + +```c +void new_w_tag(ecs_world_t *t, ecs_type_t ecs_type(Tag)) { + // ecs_new uses an ecs_type_t + ecs_new(world, Tag); +} + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + new_w_tag(world, ecs_type(Tag)); + + ecs_fini(world); +} +``` + +For functions that require an `ecs_entity_t` handle, the tag variable names are not escaped, since they do not clash with a C type name. An example: + +```c +void add_tag(ecs_world_t *t, ecs_entity_t e, ecs_entity_t Tag) { + ecs_add_id(world, e, Tag); +} + +int main() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_new(world, 0); + add_tag(world, e, Tag); + + ecs_fini(world); +} +``` + +Anyone who paid careful attention to this example will notice that the `ecs_add_id` operation accepts two regular entities. + +### Switchable tags +Switchable tags are sets of regular tags that can be added to an entity, except that only one of the set can be active at the same time. This is particularly useful when storing state machines. Consider the following example: + +```c +/* Create a Movement switch machine with 3 cases */ +ECS_TAG(world, Standing); +ECS_TAG(world, Walking); +ECS_TAG(world, Running); +ECS_TYPE(world, Movement, Standing, Walking, Running); + +/* Create a few entities with various state combinations */ +ecs_entity_t e = ecs_new(world, 0); + +/* Add the switch to the entity. This lets Flecs know that only one of the tags + * in the Movement type may be active at the same time. */ +ecs_add_id(world, e, ECS_SWITCH | Movement); + +/* Add the Standing case to the entity */ +ecs_add_id(world, e, ECS_CASE | Standing); + +/* Add the Walking case to the entity. This removes Standing */ +ecs_add_id(world, e, ECS_CASE | Walking); + +/* Add the Running case to the entity. This removes Walking */ +ecs_add_id(world, e, ECS_CASE | Running); +``` + +Switchable tags aren't just convenient, they are also very fast, as changing a case does not move the entity between archetypes like regular tags do. This makes switchable components particularly useful for fast-changing data, like states in a state machine. Systems can query for switchable tags by using the `SWITCH` and `CASE` roles: + +```c +/* Subscribe for all entities that are Walking, and have the switch Direction */ +ECS_SYSTEM(world, Walk, EcsOnUpdate, CASE | Walking, SWITCH | Direction); +``` + +See the [switch example](https://github.com/SanderMertens/flecs/blob/master/examples/c/44_switch/src/main.c) for more details. + +## Queries +Queries allow an application to iterate entities that match a component expression, called a signature (see "Signatures"). Queries are stateful, in that they are registered with the world, and keep track of a list of entities (archetypes) that they match with. Whenever a new combination of entities is introduced (usually through an `ecs_add` or `ecs_remove` operation) it will be matched with the system, and if it matches, stored in a list with matched tables. This continuous matching process means that when an application starts iterating the query, it does not need to evaluate the query signature, which makes queries the most performant way to iterate entities. + +A query can be used like this: + +```c +// Create a query for all entities with Position, Velocity +ecs_query_t *query = ecs_query_new(world, "Position, Velocity"); + +// Create iterator for query +ecs_iter_t it = ecs_query_iter(query); + +// Iterate all the matching archetypes +while (ecs_query_next(&it)) { + // Get the component arrays + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + // Iterate the entities in the archetype + for (int i = 0; i < it.count, i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} +``` + +When an application is iterating the query, it can obtain pointers to the component arrays using the `ecs_term` function, which accepts the iterator, component name and the index of the component in the signature, which is offset by one. In the above example, `1` points to `Position`, which is the first component in the signature. and `2` points to `Velocity` which is the second. + +Each time the `ecs_query_next` function returns true, the iterator contains entities that all have the same set of components, or belong to the same archetype, or table. The `count` member of the iterator contains the number of entities in the current table. An application can access the entity identifiers being iterated over with the `entities` member of the iterator: + +```c +while (ecs_query_next(&it)) { + for (int i = 0; i < it.count, i ++) { + printf("Entity %s\n", ecs_get_name(world, it.entities[i])); + } +} +``` + +### Change tracking +An application is able to see whether the entities and components matched with a query have changed since the last iteration with the `ecs_query_changed` function. When this function is invoked for the first time for a query it will always return true. The function should be invoked before obtaining an iterator to the query, as obtaining an iterator resets the state required for change tracking. An example: + +```c +if (ecs_query_changed(q)) { + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + // ... + } +} +``` + +## Signatures +**NOTE**: This section describes the legacy query DSL. For documentation on the new query DSL and APIs, see the [Queries manual](Queries.md). The following documentation describes the legacy query language that will be deprecated in the next major version of flecs. + +The query signature accepts a wide range of operators and options that allow an application to determine with fine granularity which entities to iterate over. The most common kind of signature is one that subscribes for entities with a set of components. An example of such a signature looks like a comma delimited list of component or tag identifiers: + +``` +Position, Velocity +``` + +Component identifiers can be namespaced, as is often the case with components from a module. If so, a signature will have to include the full path to the component, separated by dots: + +``` +my.namespace.Position, my.namespace.Velocity +``` + +Signatures may contain type roles. Type roles have the same identifier as the API constants, without the `ECS` prefix. + +``` +SWITCH | Movement, my.namespace.Position +``` + +The subset of signatures described so far are called "type expressions", and can be used not only for queries, but also for defining types, usually in combination with the `ECS_TYPE` macro: + +```c +ECS_TYPE(world, MyType, SWITCH | Movement, my.namespace.Position); +``` + +The features described from here are not allowed in type expressions, and may only occur for queries and systems. + +### Source modifiers +By default, components in a query are matched against the entities in a table. Source modifiers allow a query to request data from other entities, typically ones that have a special relationship with the matched entities. A source modifier can be specified with the colon (`:`) character. The following source modifiers can be provided in a signature: + +Modifier | Description +---------|------------ +OWNED | Match only owned components (default) +SHARED | Match only shared components +ANY | Match owned or shared components +PARENT | Match component from parent +CASCADE | Match component from parent, iterate breadth-first +SYSTEM | Match component added to system +Entity | Get component directly from a named entity +$ | Match singleton component +Nothing | Do not get the component from an entity, just pass in handle + +This is an example of a query that requests the `Position` component from both the entity and its parent: + +``` +PARENT:Position, Position +``` + +#### OWNED +The `OWNED` modifier matches a component if the entity owns the component. Whenever a component is added to an entity, it is said that the entity owns the component: + +```c +ecs_entity_t e = ecs_new(world, 0); + +// Entity 'e' owns Position +ecs_add(world, e, Position); +``` + +Components can be shared, when an entity has a `IsA` relationship, in which case an entity may share components with a base entity: + +```c +ecs_entity_t base = ecs_new(world, 0); +ecs_add(world, base, Position); + +// Entity 'e' does not own Position, Position is shared +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); +``` + +An example of a signature expression with `OWNED: + +``` +OWNED:Position, OWNED:Velocity +``` + +`OWNED` is the default modifier, which means that if an application does not provide a source modifier, `OWNED` is used. + +#### SHARED +The `SHARED` modifier only matches shared components. A shared component is a component that the entity inherits from a base entity, through an `IsA` relationship (see the examples in `OWNED`). + +When a query or system matches with a `SHARED` component, the `ecs_term` function does not provide an array. Instead it provides a pointer to the shared component which is shared with the entities in the table being iterated over: + +```c +// Request Position, Velocity where Velocity must be shared +ecs_query_t *query = ecs_query_new(world, "Position, SHARED:Velocity"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + // Pointer is an array, Velocity is a pointer + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count, i ++) { + // Access velocity as pointer, not as array + p[i].x += v->x; + p[i].y += v->y; + } +} +``` + +#### ANY +The `ANY` modifier matches both `OWNED` and `SHARED` components. When `ANY` is used, a system should use the `ecs_is_owned` function to test whether it is owned or shared: + +```c +// Request Position, Velocity where Velocity must be shared +ecs_query_t *query = ecs_query_new(world, "Position, ANY:Velocity"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + // Test outside of loop for better performance + if (ecs_is_owned(it, 2)) { + // Velocity is owned, access as array + for (int i = 0; i < it.count, i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + } else { + // Velocity is shared, access as pointer + for (int i = 0; i < it.count, i ++) { + p[i].x += v->x; + p[i].y += v->y; + } + } +} +``` + +#### PARENT +The `PARENT` modifier requests a component from the parent of an entity. This works with entities that have a `ChildOf` relationship: + +```c +ecs_entity_t parent = ecs_new(world, 0); +ecs_add(world, parent, Position); + +ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); +``` + +Queries that use a `PARENT` column should access the column data as if it were a `SHARED` column, as a pointer and not as an array: + +```c +ecs_query_t *query = ecs_query_new(world, "Position, PARENT:Position"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + // 1st Position is array, 2nd one is pointer + Position *p = ecs_term(&it, Position, 1); + Position *p_parent = ecs_term(&it, Position, 2); + + for (int i = 0; i < it.count, i ++) { + p[i].x += p_parent->x; + p[i].y += p_parent->y; + } +} +``` + +If an entity does not have a parent with the specified component, the query will not match with the entity. + +#### CASCADE +The `CASCADE` modifier is like the `PARENT` modifier, except that it iterates entities breadth-first, calculated by counting the number of parents from an entity to the root. Another difference with `PARENT` is that `CASCADE` matches with the root of a tree, which does not have a parent with the specified component. This requires `CASCADE` queries to check if the parent component is available: + +```c +ecs_query_t *query = ecs_query_new(world, "Position, CASCADE:Position"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Position *p_parent = ecs_term(&it, Position, 2); + + if (p_parent) { + for (int i = 0; i < it.count, i ++) { + p[i].x += p_parent->x; + p[i].y += p_parent->y; + } + } +} +``` + +The `CASCADE` modifier is useful for systems that need a certain parent component to be written before the child component is written, which is the case when, for example, transforming from local coordinates to world coordinates. + +#### SYSTEM +The `SYSTEM` modifier automatically adds a component to the system that can be retrieved from the system, and is an easy way to pass data to a system. It can be used like this in a signature: + +``` +SYSTEM:MySystemContext +``` + +This adds the `MySystemContext` component to the system. An application can get/set this component by using regular ECS operations: + +```c +typedef struct { + int value; +} MySystemContext; + +ECS_SYSTEM(world, MySystem, EcsOnUpdate, Position, SYSTEM:MySystemContext); + +ecs_set(world, MySystem, MySystemContext, { .value = 10 }); +``` + +When iterating the system, the component can be retrieved just like other components: + +```c +void MySystem(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + MySystemContext *ctx = ecs_term(it, MySystemContext, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += ctx->value; // Note that this is a pointer, not an array + } +} +``` + +#### Entity +A query can request a component from a named entity directly as is shown in the following example: + +```c +// Shortcut to creating a new named entity +ecs_entity_t e = ecs_set_name(world, 0, "MyEntity"); +ecs_set(world, e, Velocity, {1, 2}); + +ecs_query_t *q = ecs_query_new(world, "Position, MyEntity:Velocity"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += v->x; + p[i].y += v->y; + } +} +``` + +If the named entity does not have the specified component, the query will not match anything. + +#### Singleton +The singleton modifier matches a component from a singleton entity. Singletons are entities that are both a component and an entity with an instance of the component. An application can set a singleton component by using the singleton API: + +```c +ecs_singleton_set(world, Game, { .max_speed = 100 }); +``` + +Alternatively the regular API can also be used: + +```c +ecs_set(world, ecs_id(Game), Game, { .max_speed = 100 }); +``` + +Singleton components can be retrieved from queries like this: + +```c +ecs_query_t *query = ecs_query_new(world, "Position, $Game"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Game *g = ecs_term(&it, Game, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += g->max_speed; + } +} +``` + +If the singleton does not exist, the query will not match anything. + +#### Nothing +The nothing modifier does not get the component from an entity, but instead just passes its identifier to a query or system. An example: + +```c +ecs_query_t *query = ecs_query_new(world, "Position, :Velocity"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + // Get component identifier from column + ecs_entity_t vel_id = ecs_term_id(&it, 2); +} +``` + +### Operators +Signatures may contain operators, which allow queries to make more granular selections of entities. The following operators are avaialble: + +Operator | Symbol | Description +---------|--------|------------ +And | `,` | All elements in AND expression must match +Or | `||` | At least one components in OR expression must match +Not | `!` | Entity should not have component +Optional | `?` | Entity may have component + +Operators can be combined with source modifiers: + +``` +Position, !PARENT:Velocity +``` + +#### AND +And is the most common operator, and allows a query to request a set of components that an entity must have in order to be matched. The AND operator takes lowest precedence, and elements in an AND expression may contain other operators. An example of a signature with an `AND` operator is: + +``` +Position, Velocity +``` + +#### OR +OR expressions allow a signature to match with one of the components in the OR expression. An example of a signature with an `OR` operator is: + +``` +Position, Velocity || Speed +``` + +In this example, any entity that has `Position`, and `Velocity` OR `Speed` will match the signature. Components in an OR expression may contain SOURCE modifiers, but the source modifier must be the same for all elements: + +``` +Position, PARENT:Velocity || PARENT:Speed +``` + +#### NOT +Not expressions allow a signature to exclude entities that have a component. An example of a signature with a `NOT` operator is: + +``` +Position, !Velocity +``` + +This signature matches all entities that have `Position`, but not have `Velocity`. An expression with a `NOT` column does not pass any data into a system, which means that the `ecs_term` function is guaranteed to return `NULL` for a column. + +#### Optional +The optional operator allows a signature to both match entities with and without a specific component. An example with an optional operator is: + +``` +Position, ?Velocity +``` + +A query with an optional column should test if the component is set before using it: + +```c +ecs_query_t *query = ecs_query_new(world, "Position, CASCADE:Position"); + +ecs_iter_t it = ecs_query_iter(query); + +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + if (v) { + for (int i = 0; i < it.count, i ++) { + p[i].x += v[i].x + p[i].y += v[i].y + } + } +} +``` + +### Access modifiers +A signature can contain access modifiers, which lets the framework know whether a system or query will read or write the component. The following access modifiers are supported: + +Modifier | Description +---------|------------ +in | The component is read only +out | The component is write only +inout | The component can both be read and written + +Access modifiers are added to a signature using angular brackets: + +``` +[out] Position, [in] Velocity +``` + +The default access modifier is `[inout]`, which by default allows a system to read and write a component, but also means Flecs cannot make optimizations in, for example, how systems can be executed in parallel. For this reason, while not mandatory, applications are encouraged to add access modifiers to systems where possible. + +## Sorting +Applications are able to access entities in order, by using sorted queries. Sorted queries allow an application to specify a component that entities should be sorted on. Sorting is enabled with the `ecs_query_order_by` function: + +```c +ecs_query_t q = ecs_query_new(world, "Position"); +ecs_query_order_by(world, q, ecs_id(Position), compare_position); +``` + +This will sort the query by the `Position` component. The function also accepts a compare function, which looks like this: + +```c +int compare_position(ecs_entity_t e1, Position *p1, ecs_entity_t e2, Position *p2) { + return p1->x - p2->x; +} +``` + +Once sorting is enabled for a query, the data will remain sorted, even after the underlying data changes. The query keeps track of any changes that have happened to the data, and if changes could have invalidated the ordering, data will be resorted. Resorting does not happen when the data is modified, which means that sorting will not decrease performance of regular operations. Instead, the sort will be applied when the application obtains an iterator to the query: + +```c +ecs_entity_t e = ecs_new(world, Position); // Does not reorder +ecs_set(world, e, Position, {10, 20}); // Does not reorder +ecs_iter_t it = ecs_query_iter(q); // Reordering happens here +``` + +The following operations mark data dirty can can trigger a reordering: +- Creating a new entity with the ordered component +- Deleting an entity with the ordered component +- Adding the ordered component to an entity +- Removing the ordered component from an entity +- Setting the ordered component +- Running a system that writes the ordered component (through an [out] column) + +Applications iterate a sorted query in the same way they would iterate a regular query: + +```c +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + for (int i = 0; i < it.count; i ++) { + printf("{%f, %f}\n", p[i].x, p[i].y); // Values printed will be in order + } +} +``` + +### Sorting algorithm +The algorithm used for the sort is a quicksort. Each table that is matched with the query will be sorted using a quicksort. As a result, sorting one query affects the order of entities in another query. However, just sorting tables is not enough, as the list of ordered entities may have to jump between tables. For example: + +Entitiy | Components (table) | Value used for sorting +--------|--------------------|----------------------- +E1 | Position | 1 +E2 | Position | 3 +E3 | Position | 4 +E4 | Position, Velocity | 5 +E5 | Position, Velocity | 7 +E6 | Position, Mass | 8 +E7 | Position | 10 +E8 | Position | 11 + +To make sure a query iterates the entities in the right order, it will iterate entities in the ordered tables to determine the largest slice of ordered entities in each table, which the query will iterate in order. Slices are precomputed during the sorting step, which means that the performance of query iteration is similar to a regular iteration. For the above set of entities, these slices would look like this: + +Table | Slice +-------------------|------- +Position | 0..2 +Position, Velocity | 3..4 +Position, Mass | 5 +Position | 6..7 + +This process is transparent for applications, except that the slicing will result in smaller contiguous arrays being iterated by the application. + +### Sorting by entity id +Instead of sorting by a component value, applications can sort by entity id by not specifying a component to the `ecs_query_order_by` function: + +```c +ecs_query_order_by(world, q, 0, compare_entity); +``` + +The compare function would look like this: + +```c +int compare_position(ecs_entity_t e1, Position *p1, ecs_entity_t e2, Position *p2) { + return e1 - e2; +} +``` + +When no component is provided in the `ecs_query_order_by` function, no reordering will happen as a result of setting components or running a system with `[out]` columns. + +## Filters +Filters allow an application to iterate through matching entities in a way that is similar to queries. Contrary to queries however, filters are not prematched, which means that a filter is evaluated as it is iterated over. Filters are therefore slower to evaluate than queries, but they have less overhead and are (much) cheaper to create. This makes filters less suitable for repeated-, but useful for ad-hoc searches where the application doesn't know beforehand which set of entities it will need. + +A filter can be used like this: + +```cpp +ECS_COMPONENT(world, Position); + +/* Create filter */ +ecs_filter_t filter = { + .include = ecs_type(Position), + .include_kind = EcsMatchAll +}; + +/* Create iterator to filter */ +ecs_iter_t it = ecs_filter_iter(world, &filter); + +while (ecs_filter_next(&it)) { + /* Because a filter does not have a signature, we need to get the component + * array by finding it in the current table */ + ecs_type_t table_type = ecs_iter_type(&it); + + /* First Retrieve the column index for Position */ + int32_t p_index = ecs_type_index_of(table_type, 0, ecs_id(Position)); + + /* Now use the column index to get the Position array from the table */ + Position *p = ecs_table_column(&it, p_index); + + /* Iterate as usual */ + for (int i = 0; i < it.count; i ++) { + printf("{%f, %f}\n", p[i].x, p[i].y); + } +} +``` + +A filter can provide an `include` and an `exclude` type, where the `include` type specifies the components the entities must have in order to match the filter, and the `exclude` type specifies the components the entity should not have. In addition to these two fields, the filter provides a `kind` field for both `include` and `exclude` types which can be one of these values: + +Option | Description +----------------|------------------------------------------------------------------------ +EcsMatchDefault | Default matching: ECsMatchAny for include, and EcsMatchAll for exclude +EcsMatchAll | The type must include/excldue all components +EcsMatchAny | The type must include/exclude one of the components +EcsMatchExact | The type must match exactly with the components of the entity + +## Systems +Systems allow the application to run logic that is matched with a set of entities every frame, periodically or as the result of some event. An example of a simple system definition is: + +```c +ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); +``` + +In this definition, `Move` is the name of the system and also fo the function that will be registered with the system. `EcsOnUpdate` is the system "phase" which indicates when the system is ran in a frame (see "Pipelines"). The part that follows is the system expression, which follows the rules as described in "Signatures"). + +A system implementation is a function with the following signature: + +```c +void Move(ecs_iter_t *it) { } +``` + +The implementation of a system is a regular query iteration: + +```c +Position *p = ecs_term(it, Position, 1); +Velocity *v = ecs_term(it, Velocity, 2); + +for (int i = 0; i < it->count, i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; +} +``` + +### Using delta_time +A system provides a `delta_time` which contains the time passed since the last frame: + +```c +Position *p = ecs_term(it, Position, 1); +Velocity *v = ecs_term(it, Velocity, 2); + +for (int i = 0; i < it->count, i ++) { + p[i].x += v[i].x * it->delta_time; + p[i].y += v[i].y * it->delta_time; +} +``` + +This is the value passed into `ecs_progress`: + +```c +ecs_progress(world, delta_time); +``` + +If 0 was provided for `delta_time`, flecs will automatically measure the time passed between the last frame and the current. + +A system may also use the `delta_system_time` member, which is the time elapsed since the last time the system was invoked. This can be useful when a system is not invoked each frame, for example when using a timer. + +### Systems and tables +A system may be invoked multiple times per frame. The reason this happens is because entities are stored in different "tables", where each table stores entities of a specific set of components. For example, all entities with components `Position, Velocity` will be stored in table A, where all entities with components `Position, Mass` are stored in table B. Tables ensure that component data is stored in contiguous arrays, and that the same index can be used for a particular entity in all component arrays. Because systems iterate component arrays directly, and because a component can be stored in more than one array, systems need to be invoked once for each table. + +The total number of tables a system will iterate over is stored in the `table_count` member of the iterator. Additionally, the `table_offset` member contains the current table being iterated over, so that a system can keep track of where it is in the iteration: + +```c +void Move(ecs_iter_t *it) { + printf("Iterating table %d / %d\n", it->table_offset, it->table_count); + // ... +} +``` + +### Monitors +A monitor is a special kind of system that is executed once when a condition becomes true. A monitor is created just like a regular system, but with the `EcsMonitor` tag: + +```c +ECS_SYSTEM(world, OnPV, EcsMonitor, Position, Velocity); +``` + +This example illustrates when the monitor is invoked: + +```c +// Condition is not true: monitor is not invoked +ecs_entity_t e = ecs_new(world, Position); + +// Condition is true for the first time: monitor is invoked! +ecs_add(world, e, Velocity); + +// Condition is still true: monitor is not invoked +ecs_add(world, e, Mass); + +// Condition is no longer true: monitor is not invoked +ecs_remove(world, e, Position); + +// Condition is true again: monitor is invoked! +ecs_add(world, e, Position); +``` + +Note that monitors are never invoked by `ecs_progress`. + +An monitor is implemented the same way as a regular system: + +```c +void OnPV(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + /* Monitor code. Note that components may not have + * been initialized when the monitor is invoked */ + } +} +``` + +### OnSet Systems +OnSet systems are ran whenever the value of one of the components the system subscribes for changes. An OnSet system is created just like a regular system, but with the `EcsOnSet` tag: + +```c +ECS_SYSTEM(world, OnSetPV, EcsOnSet, Position, Velocity); +``` + +This example illustrates when the monitor is invoked: + +```c +ecs_entity_t e = ecs_new(world, 0); + +// The entity does not have Velocity, so system is not invoked +ecs_set(world, e, Position, {10, 20}); + +// The entity has both components, but Velocity is not set +ecs_add(world, e, Velocity); + +// The entity has both components, so system is invoked! +ecs_set(world, e, Velocity, {1, 2}); + +// The entity has both components, so system is invoked! +ecs_set(world, e, Position, {11, 22}); +``` + +An OnSet system is implemented the same way as a regular system: + +```c +void OnSetPV(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + /* Trigger code */ + } +} +``` + +The opposite of an `EcsOnSet` system is an `EcsUnSet` system: + +```c +ECS_SYSTEM(world, UnSetP, EcsUnSet, Position); +``` + +An UnSet system is invoked when an entity no longer has a value for the specified component: + +```c +ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + +// The UnSet system is invoked +ecs_remove(world, e, Position); +``` + +OnSet and UnSet systems are typically invoked when components are set and removed, but there are two edge cases: + +- A component is removed but the entity inherits a value for the component from a base entity. In this case OnSet is invoked, because the value for the component changed. +- The entity does not have the component, but the base that has the component is removed. In this case UnSet is invoked, since the entity no longer has the component. + +## Triggers +Triggers are callbacks that are executed when a component is added or removed from an entity. Triggers are similar to systems, but unlike systems they can only match a single component. This is an example of a trigger that is executed when the Position component is added: + +```c +ECS_TRIGGER(world, AddPosition, EcsOnAdd, Position); +``` + +The implementation of the trigger looks similar to a system: + +```c +void AddPosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + printf("Position added\n"); + } +} +``` + +## Modules +Modules allow an application to split up systems and components into separate decoupled units. The purpose of modules is to make it easier to organize systems and components for large projects. Additionally, modules also make it easier to split off functionality into separate compilation units. + +A module consists out of a few parts: + +- A module type (struct) that stores handles to the contents in the modules +- A macro to declare module contents as local variables in the scope where it is imported +- An import function that loads the module contents for a world + +The module type and macro are typically located in the a separate module header file, and look like this for a module named "Vehicles": + +```c +typedef struct Car { + float speed; +} Car; + +typedef struct Bus { + float speed; +} Bus; + +typedef struct MotorCycle { + float speed; +} MotorCycle; + +typedef struct Vehicles { + /* Components are declared with ECS_DECLARE_COMPONENT */ + ECS_DECLARE_COMPONENT(Car); + ECS_DECLARE_COMPONENT(Bus); + ECS_DECLARE_COMPONENT(MotorCycle); + + /* Tags are declared with ECS_DECLARE_ENTITY */ + ECS_DECLARE_ENTITY(Moving); + + /* Systems are also declared with ECS_DECLARE_ENTITY */ + ECS_DECLARE_ENTITY(Move); +}; + +/* Forward declaration to the import function */ +void VehiclesImport(ecs_world_t *world); + +/* The ImportHandles macro mimics the module struct */ +#define VehiclesImportHandles(handles)\ + ECS_IMPORT_COMPONENT(handles, Car);\ + ECS_IMPORT_COMPONENT(handles, Bus);\ + ECS_IMPORT_COMPONENT(handles, MotorCycle);\ + ECS_IMPORT_ENTITY(handles, Moving);\ + ECS_IMPORT_ENTITY(handles, Move); +``` + +The import function for this module would look like this: + +```c +void VehiclesImport(ecs_world_t *world) { + /* Define the module */ + ECS_MODULE(world, Vehicles); + + /* Declare components, tags and systems as usual */ + ECS_COMPONENT(world, Car); + ECS_COMPONENT(world, Bus); + ECS_COMPONENT(world, MotorCycle); + ECS_TAG(world, Moving); + ECS_SYSTEM(world, Move, EcsOnUpdate, Car, Moving); + + /* Export them so that they are assigned to the module struct */ + ECS_EXPORT_COMPONENT(world, Car); + ECS_EXPORT_COMPONENT(world, Bus); + ECS_EXPORT_COMPONENT(world, Motorcycle); + ECS_EXPORT_ENTITY(world, Moving); + ECS_EXPORT_ENTITY(world, Move); +} +``` + +After the module has been defined, it can be imported in an application like this: + +```c +ecs_world_t *world = ecs_init(); + +/* Import module, which invokes the module import function */ +ECS_IMPORT(world, Vehicles); + +/* The module contents can now be used */ +ecs_entity_t e = ecs_new(world, Car); +``` + +Module contents are namespaced, which means that the identifiers of the contenst of the module (components, tags, systems) are stored in the scope of the module. For the above example module, everything would be stored in the `vehicles` scope. To resolve the `Car` component by name, an application would have to do: + +```c +ecs_entity_t car_entity = ecs_lookup_fullpath(world, "vehicles.Car"); +``` + +Note that even though the module name is specified with uppercase, the name is stored with lowercase. This is because the naming convention for modules in C is PascalCase, whereas the stored identifiers use snake_case. If a module name contains several uppercase letters, this will be translated to a nested module. For example, the C module name `MySimpleModule` will be translated to `my.simple.module`. + +### Modules in C++ +A module in C++ is defined as a class where the module contents are defined in the constructor. The above Vehicles module would look like this in C++: + +```cpp +/* In C++ it is more convenient to define tags as empty structs */ +struct Moving { }; + +/* Module implementation */ +class vehicles { +public: + vehicles(flecs::world& world) { + flecs::module<Vehicles>(world, "vehicles"); + + m_car = flecs::component<Car>(world, "Car"); + m_bus = flecs::component<Bus>(world, "Bus"); + m_motor_cycle = flecs::component<MotorCycle>(world, "MotorCycle"); + + m_moving = flecs::component<Moving>(world, "Moving"); + m_move = flecs::system<Car, Moving>(world, "Move") + .each([](flecs::entity e, Car &car, Moving&) { + /* System implementation */ + }); + } + + flecs::entity m_car; + flecs::entity m_bus; + flecs::entity m_motor_cycle; + flecs::entity m_moving; + flecs::entity m_move; +} +``` + +An application can import the module in C++ like this: + +```cpp +flecs::world world; +flecs::import<vehicles>(world); +``` + +## Hierarchies +Entities in Flecs can be organized in hierarchies, which is useful when for example constructing a scene graph. To create hierarchies, applications can add `ChildOf` relationships to entities. This creates a relationship between a parent entity and a child entity that the application can later traverse. This is an example of a simple hierarchy: + +```c +ecs_entity_t parent = ecs_new(world, 0); +ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); +``` + +`ChildOf` relationships can be added and removed dynamically, similar to how components can be added and removed: + +```c +ecs_add_pair(world, child, EcsChildOf, parent); +ecs_remove_pair(world, child, EcsChildOf, parent); +``` + +`ChildOf` relationships can also be created through the `ECS_ENTITY` macro: + +```c +ECS_ENTITY(world, parent, 0); +ECS_ENTITY(world, child, (ChildOf, parent)); +``` + +### Iteration +Applications can iterate hierarchies breadth first with the `ecs_scope_iter` API in C, and the `children()` iterator in C++. This example shows how to iterate all the children of an entity: + +```cpp +ecs_iter_t it = ecs_scope_iter(world, parent); + +while(ecs_scope_next(&it)) { + for (int i = 0; i < it.count; i ++) { + ecs_entity_t child = it.entities[i]; + char *path = ecs_get_fullpath(world, child); + printf(" - %s\n", path); + free(path); + } +} +``` + +Additionally, applications can request all children that have a specific set of components, by adding a filter to the scope iterator: + +```c +ecs_filter_t f = { + .include = ecs_type(Position) +}; + +// Iterate all children that have Position +ecs_iter_t it = ecs_scope_iter_w_filter(world, parent, &f); +``` + +### Hierarchical queries +Queries and systems can request data from parents of the entity being iterated over with the `PARENT` modifier: + +```c +// Iterate all entities with Position that have a parent that also has Position +ecs_query_t *q = ecs_query_new(world, "PARENT:Position, Position"); +``` + +Additionally, a query can iterate the hierarchy in breadth-first order by providing the `CASCADE` modifier: + +```c +// Iterate all entities with Position that have a parent that also has Position +ecs_query_t *q = ecs_query_new(world, "CASCADE:Position, Position"); +``` + +This does two things. First, it will iterate over all entities that have Position and that _optionally_ have a parent that has `Position`. By making the parent component optional, it is ensured that if an application is iterating a tree of entities, the root is also included. Secondly, the query iterates over the children in breadth-first order. This is particularly useful when writing transform systems, as they require parent entities to be transformed before child entities. + +See the [Signatures](Signatures) section for more details. + +### Path identifiers +When entities in a hierarchy have names assigned to them, they can be looked up with path expressions. A path expression is a list of entity names, separated by a scope separator character (by default a `.`, and `::` in the C++ API). This example shows how to request the path expression from an entity: + +```c +ECS_ENTITY(world, parent, 0); +ECS_ENTITY(world, child, (ChildOf, parent)); + +char *path = ecs_get_fullpath(world, child); +printf("%s\n", path); // Prints "parent.child" +free(path); +``` + +To lookup an entity using a path, use `ecs_lookup_fullpath`: + +```c +ecs_entity_t e = ecs_lookup_fullpath(world, "parent.child"); +``` + +Applications can also lookup entities using a relative path expression: + +```c +ecs_entity_t e = ecs_lookup_path(world, parent, "child.grand_child"); +``` + +Additionally, applications can specify a custom path separator when looking up or requesting paths: + +```c +// Lookup child::grand_child relative to parent +ecs_entity_t e = ecs_lookup_path_w_sep( + world, parent, "child::grand_child", "::", "::"); + +// Get path of child relative to parent +char *path = ecs_get_path_w_sep(world, parent, child, "::", "::"); +``` + +Note that the path separator is provided twice, once for the prefix and once for the separator. This lets the API correctly handle expressions like `::parent::child::grand_child"`. + +### Scoping +Applications can set a default scope with the `ecs_set_scope` function, so that all operations are evaluated relative to a scope. The scope is set on a stage, which makes it thread safe when executed from within a flecs worker thread. This example shows how to set the scope: + +```c +ecs_entity_t parent = ecs_new(world, 0); + +// Set the current scope to the parent +ecs_entity_t prev_scope = ecs_set_scope(world, parent); + +// This entity is created as child of parent +ecs_entity_t child = ecs_new(world, 0); + +// Look for "child" relative to parent +ecs_entity_t e = ecs_lookup_fullpath(world, "child"); + +// It's good practice to restore the previous scope +ecs_set_scope(prev_scope); +``` + +Modules automatically set the scope to the module itself, so that the module acts as a namespace for its contents. + +### Paths and signatures +When referencing entities or components in a signature or type expression that are not stored in the root, an application will have to provide the path. Signatures and type expressions always use the dot (`.`) as separator. For example, if a component "Position" is defined in the module "transform", a system subscribing for the component would have to be defined like this: + +```c +ECS_SYSTEM(world, Move, EcsOnUpdate, transform.Position); +``` + +The same goes for other parts of the API that accept a type expression, like `ECS_ENTITY` or `ECS_TYPE`: + +```c +ECS_TYPE(world, Movable, transform.Position); +``` + +If the system would be defined in the same scope as the `Position` component, it would not need to specify the path: + +```c +ECS_ENTITY(world, transform, 0); + +ecs_entity_t prev_scope = ecs_set_scope(world, transform); + +ECS_COMPONENT(world, Position); + +// System is in the same scope, no need to add "transform" +ECS_SYSTEM(world, MoveInScope, EcsOnUpdate, Position); + +ecs_set_scope(world, prev_scope); + +// This system is not in the same scope, and needs to add transform +ECS_SYSTEM(world, MoveNotInScope, EcsOnUpdate, transform.Position); +``` + +## Inheritance +Inheritance is the ability to share components between entities by _inheriting_ from them, by using the `IsA` relation. This is a simple example in the C API: + +```c +// Create a base entity +ecs_entity_t base = ecs_new(world, 0); +ecs_set(world, base, Position, {10, 20}); + +// Derive from base +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + +// e now shares Position with base +ecs_get(world, base, Position) == ecs_get(world, e, Position); // 1 +``` + +`IsA` relationships can be added and removed dynamically, similar to how components can be added and removed: + +```c +ecs_add_id(world, e, (IsA, base)); +ecs_remove_id(world, e, (IsA, base)); +``` + +`IsA` relationships can also be created through the `ECS_ENTITY` macro: + +```c +ECS_ENTITY(world, base, Position); +ECS_ENTITY(world, e, (IsA, base)); +``` + +`IsA` relationships can be nested: + +```c +ecs_entity_t base = ecs_new(world, 0); +ecs_set(world, base, Position, {10, 20}); + +ecs_entity_t derived = ecs_new_w_pair(world, EcsIsA, base); + +// Derive from "derived" which is itself derived from base +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, derived); + +// All three entities now share Position +ecs_get(world, base, Position) == ecs_get(world, e, Position); // 1 +ecs_get(world, base, Position) == ecs_get(world, derived, Position); // 1 +``` + +### Overriding +Derived entities can override components from their base by adding the component as they would normally. When overriding a component, the value of the base component is copied to the entity. This example shows how a derived entity overrides the Position component: + +```c +// Shortcut for creating a base entity and setting Position +ecs_entity_t base = ecs_set(world, 0, Position, {10, 20}); + +// Derive from the base +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + +// Override Position +ecs_add(world, e, Position); + +// Position component no longer matches with base +ecs_get(world, base, Position) != ecs_get(world, e, Position); // 1 + +// Prints {10, 20} +const Position *p = ecs_get(world, e, Position); +printf("{%f, %f}\n", p->x, p->y); +``` + +When an entity shared a component from a base entity, we say that the component is "shared". If the component is not shared, it is "owned". After an entity overrides a component, it will own the component. + +It is possible to remove an override, in which case the component will be shared with the base entity again: + +```c +// Removes override on Position +ecs_remove(world, e, Position); + +// Position is again shared with base +ecs_get(world, base, Position) == ecs_get(world, e, Position); // 1 +``` + +Overrides work with nested `IsA` relationships: + +```c +// Shortcut for creating a base entity and setting Position +ecs_entity_t base = ecs_new(world, 0); +ecs_set(world, base, Position, {10, 20}); +ecs_set(world, base, Velocity, {1, 1}); + +// Create derived entity, override Position +ecs_entity_t derived = ecs_new_w_pair(world, EcsIsA, base); +ecs_add(world, base, Position); + +// Derive from 'derived', which is derived from base +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, derived); + +// The entity now shares Position from derived, and Velocity from base +``` + +### Automatic overriding +In some scenarios it is desirable that an entity is initialized with a specific set of values, yet does not share the components from the base entity. In this case the derived entity can override each component individually, but this can become hard to maintain as components are added or removed to the base. This can be achieved by marking components as owned. Consider the following example: + +```c +// Create a base. Simply deriving the base will share the component, but not override it. +ecs_entity_t Base = ecs_set(world, 0, Position, {10, 20}); + +// Mark as OWNED. This ensures that when base is derived from, Position is overridden +ecs_add_id(world, world, Base, ECS_OWNED | ecs_id(Position)); + +// Create entity from BaseType. This adds the IsA relationship in addition +// to overriding Position, effectively initializing the Position component for the entity. +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Base); +``` + +The combination of instancing, overriding and OWNED is one of the fastest and easiest ways to create an entity with a set of initialized components. The OWNED relationship can also be specified inside type expressions. The following example is equivalent to the previous one: + +```c +ECS_ENTITY(world, Base, Position, OWNED | Position); + +ecs_set(world, Base, Position, {10, 20}); + +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Base); +``` + +### Inheritance hierarchies +If a base entity has children, derived entities of that base entity will, when the `IsA` relationship is added, acquire the same set of children. Take this example: + +```c +ecs_entity_t parent = ecs_new(world, 0); +ecs_entity_t child_1 = ecs_new_w_pair(world, EcsChildOf, parent); +ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent); + +// Derive from parent, two childs are added to the entity +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, parent); +``` + +The children that are copied to the entity will have exactly the same set of components as the children of the base. For example, if the base child has components `Position, Velocity`, the derived child will also have `Position, Velocity`. Furthermore, the values of the base child components will be copied to the entity child: + +```c +ecs_entity_t parent = ecs_new(world, 0); +ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); +ecs_set_name(world, child, "Child"); // Give child a name, so we can look it up +ecs_set(world, child, Position, {10, 20}); + +// Derive from parent, two childs are added to the derived entity +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, parent); +ecs_entity_t e_child = ecs_lookup_path(world, e, "Child"); +const Position *p = ecs_get(world, e_child, Position); +printf("{%f, %f}\n", p->x, p->y); // Prints {10, 20} + +// The components are not shared with the derived child! +ecs_get(world, child, Position) != ecs_get(world, e_child, Position); // 1 +``` + +Since the children of the derived entitiy have the exact same components as the base children, their components are not shared. Component sharing between children is possible however, as `IsA` relationships are also copied over to the child of the derived entity: + +```c +ecs_entity_t parent = ecs_new(world, 0); + +// Create child base from which we will share components +ecs_entity_t child_base = ecs_new(world, 0); +ecs_set(world, child_base, Position, {10, 20}); +ecs_set_name(world, child, "Child"); + +// Create actual child that inherits from the child base +ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); +ecs_add_pair(world, child, EcsIsA, child_base); + +// Inherit from parent, two childs are added to the entity +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, parent); +ecs_entity_t e_child = ecs_lookup_path(world, e, "Child"); + +// The component is now shared with the child and child_base +ecs_get(world, child, Position) == ecs_get(world, e_child, Position); // 1 +``` + +### Prefabs +Prefabs are entities that can be used as templates for other entities. Prefabs are regular entities, except that they are not matched by default with systems. To create a prefab, add the `EcsPrefab` tag when creating an entity: + +```c +ecs_entity_t prefab = ecs_new_w_entity(world, EcsPrefab); +``` + +The `EcsPrefab` tag can also be added or removed dynamically: + +```c +ecs_add_id(world, prefab, EcsPrefab); +ecs_remove_id(world, prefab, EcsPrefab); +``` + +Prefabs can also be created with the `ECS_PREFAB` macro: + +```c +ECS_PREFAB(world, prefab, Position, Velocity); +``` + +To instantiate a prefab, an application can use the `IsA` relation: + +```c +ecs_entity_t e = ecs_new_w_pair(world, (IsA, prefab)); +``` + +To ensure that entities that inherit from a prefab don't also inherit the `Prefab` tag (which would cause them to not get matched with systems), the `Prefab` tag does not propagate to derived entities. This is illustrated in the following example: + +```c +ECS_PREFAB(world, prefab, Position); + +ecs_has(world, prefab, EcsPrefab); // true +ecs_has(world, prefab, Position); // true + +ecs_entity_t e = ecs_new_w_pair(world, (IsA, prefab)); +ecs_has(world, e, EcsPrefab); // false +ecs_has(world, e, Position); // true +``` + +## Deferred operations +Applications can defer entity with the `ecs_defer_begin` and `ecs_defer_end` functions. This records all operations that happen inside the begin - end block, and executes them when `ecs_defer_end` is called. Deferred operations are useful when an application wants to make modifications to an entity while iterating, as doing this without deferring an operation could modify the underlying data structure. An example: + +```c +ecs_defer_begin(world); + ecs_entity_t e = ecs_new(world, 0); + ecs_add(world, e, Position); + ecs_set(world, e, Velocity, {1, 1}); +ecs_defer_end(world); +``` + +The effects of these operations will not be visible until the `ecs_defer_end` operation. + +There are a few things to keep in mind when deferring: +- creating a new entity will always return a new id which increases the last used id counter of the world +- `ecs_get_mut` returns a pointer initialized with the current component value, and does not take into account deferred set or get_mut operations +- if an operation is called on an entity which was deleted while deferred, the operation will ignored by `ecs_defer_end` +- if a child entity is created for a deleted parent while deferred, the child entity will be deleted by `ecs_defer_end` + +## Staging +When an application is processing the world (using `ecs_progress`) the world enters a state in which all operations are automatically deferred. This ensures that systems can call regular operations while iterating entities without modifying the underlying storage. The queued operations are merged by default at the end of the frame. When using multiple threads, each thread has its own queue. Queues of different threads are processed sequentially. + +By default this means that an application will not see the effects of an operation until the end of a frame. When this is undesirable, an application can add `[in]` and `[out]` anotations to a system signature to force a merging the queues mid-frame. When using multiple threads this will represent a synchronization point. Take this (somewhat contrived) example with two systems, without annotations: + +```c +// Sets velocity using ecs_set +ECS_SYSTEM(world, SetVelocity, EcsOnUpdate, Position, :Velocity); + +// Adds Velocity to Position +ECS_SYSTEM(world, Move, EcsOnUpdate, Position, [in] Velocity); +``` + +With the following implementation for `SetVelocity`: + +```c +void SetVelocity(ecs_iter_t *it) { + ecs_entity_t ecs_id(Velocity) = ecs_term_id(it, 2); + + for (int i = 0; i < it->count; i ++) { + ecs_set(world, it->entities[i], Velocity, {1, 2}); + } +} +``` + +As `SetVelocity` is using `ecs_set` to set the `Velocity`, the effect of this operation will not be visible until the end of the frame, which means that the `Move` operation will use the `Velocity` value of the previous frame. An application can enforce that the queue is flushed before the `Move` system by annotating the system like this: + +``` +ECS_SYSTEM(world, SetVelocity, EcsOnUpdate, Position, [out] :Velocity); +``` + +Notice the `[out]` annotation that has been added to the `:Velocity` argument. This indicates to flecs that the system will be deferring operations that write the `Velocity` component, and as a result of that the queue will be flushed before `Velocity` is read. Since the `Move` system is reading the `Velocity` component, the queue will be flushed before the `Move` system is executed. + +Note that merging is expensive, especially in multithreaded applications, and should be minimized whenever possible. + +In some cases it can be difficult to predict which components a system will write. This typically happens when a system deletes an entity (all components of the entity will be "written") or when a new entity is created from a prefab and components are overridden automatically. When these operations cannot be deferred, a system can force a sync point without having to specify all possible components that can be written by using a wildcard: + +```c +ECS_SYSTEM(world, DeleteEntity, EcsOnUpdate, Position, [out] :*); +``` + +This is interpreted as the system may write any component, and forces a sync point. + +## Pipelines +A pipeline defines the different phases that are executed for each frame. By default an application uses the builtin pipeline which has the following phases: + +- EcsOnLoad +- EcsPostLoad +- EcsPreUpdate +- EcsOnUpdate +- EcsOnValidate +- EcsPostUpdate +- EcsPreStore +- EcsOnStore + +These phases can be provided as an argument to the `ECS_SYSTEM` macro: + +```c +// System ran in the EcsOnUpdate phase +ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + +// System ran in the EcsOnValidate phase +ECS_SYSTEM(world, DetectCollisions, EcsOnValidate, Position); +``` + +An application can create a custom pipeline, like is shown here: + +```c +// Create a tag for each phase in the custom pipeline. +// The tags must be created in the phase execution order. +ECS_TAG(world, BeforeFrame); +ECS_TAG(world, OnFrame); +ECS_TAG(world, AfterFrame); + +// Create the pipeline +ECS_PIPELINE(world, MyPipeline, BeforeFrame, OnFrame, AfterFrame); + +// Make sure the world uses the correct pipeline +ecs_set_pipeline(world, MyPipeline); +``` + +Now the application can create systems for the custom pipeline: + +```c +// System ran in the OnFrame phase +ECS_SYSTEM(world, Move, OnFrame, Position, Velocity); + +// System ran in the AfterFrame phase +ECS_SYSTEM(world, DetectCollisions, AfterFrame, Position); + +// This will now run systems in the custom pipeline +ecs_progress(world, 0); +``` + +## Time management + +## Timers + +## Snapshots + +## Serialization + +## Bulk operations + +## Statistics + +## Threading +Applications can multithread systems by configuring the number of threads for a world. The approach to multithreading is simple, but does not require locks and works well in applications that have "pure" ECS systems, that is systems that only modify the components subscribed for in their signature. + +When a world has multiple threads, each thread will run all systems. Each system will ensure that it only processes a subset of the entities that is allocated to the thread. It does this by dividing up each table into slices of equal size, and assigning those slices to the threads. Since entities do not move around inbetween synchronization points, this approach ensures that each entity will always be allocated to the same thread. When systems only access components that are queried for, race conditions cannot occur without relying on locking. + +Threads are created when the `ecs_set_threads` function is invoked. An application may change the number of threads by repeatedly invoking this function, as long as the world is not progressing. Threads are not recreated for each frame to reduce the overhead of multithreading. Instead threads will be signalled by the main thread when a frame starts, and the main thread will wait on the threads before ending the frame. + +No structural changes (adding/removing components, or deleting an entity) are allowed while a thread is evaluating the systems. If a system does a structural change, it is deferred until the next synchronization point. During synchronization, all deferred operations will be flushed by the main thread. + +By default there is only a single synchronization point at the end of the frame, and a thread will run all of its systems to completion for each frame. This parallelizes extremely well, even in the case where there are lots of systems with small workloads, as all the logic for a frame can be executed without any waiting or taking any locks. If a system has deferred structural changes that are required by a subsequent system however, a mid-frame synchronization point may be necessary. In this case an application can annotate system signatures to enforce synchronization points, as is described in (Staging)[#staging]. The advantage of this approach is that synchronization points are not explicitly created, but automatically derived, which prevents having to specify explicit dependencies between systems. + +This approach does have some obvious limitations. All systems are parallelized, which can cause problems when a system's logic needs to be executed for example on the main thread (as is often the case for rendering logic). Additionally, if a system reads from component references, as is the case with systems that retrieve components from prefabs or parent entities, this approach can introduce race conditions where a component value is read while it is being updated. These are known issues, and improvements to the threading framework are scheduled for future versions. + +## Tracing + +## Debug API + +## OS Abstraction API diff --git a/fggl/ecs2/flecs/docs/Queries.md b/fggl/ecs2/flecs/docs/Queries.md new file mode 100644 index 0000000000000000000000000000000000000000..e14e480e8de7c1c558e9d7afd587682a6f5c6195 --- /dev/null +++ b/fggl/ecs2/flecs/docs/Queries.md @@ -0,0 +1,1184 @@ +# Queries +Queries are the mechanism that allow applications to get the entities that match with a certain set of conditions. Queries can range from simple lists of components to complex expressions that efficiently traverse an entity graph. This manual explains the ins & outs of how to use them. + +**NOTE**: this manual describes queries as they are intended to work. The actual implementation may not have support for certain combinations of features. When an application attempts to use a feature that is not yet supported, an `UNSUPPORTED` error will be thrown. + +**NOTE**: the description of filters in this manual refers to the new rule parser which has not yet merged with master. + +## Query kinds +Flecs has two different kinds of queriers: cached and uncached. The differences are described here. Note that when "query" is mentioned in the other parts of the manual it always refers to all query kinds, unless explicitly mentioned otherwise. + +### Filter (uncached) +A filter is an uncached query that is cheap to create and finds matching entities as it is being iterated. The performance of a filter can be roughly thought of as having to do a hashmap lookup per filter term. The first hashmap lookup retrieves a list of archetypes that matches with the first term. Then the filter does a hashmap lookup for the second term. If it succeeds, the archetype that is being evaluated matches the second term, and we move on to the next term. If the hashmap lookup does not succeed, we go back to our initial list of archetypes and move on to the next one. + +In pseudo code, filter evaluation roughly looks like this: + +```python +Archetype archetypes[] = filter.get_archetypes_for_first_term(); +for archetype in archetypes: + bool match = true; + for each term in filter.range(1, filter.length): + if !archetype.match(term): + match = false; + break; + if match: + yield archetype; +``` + +An application then iterates the archetype, which can contain 1...N entities. + +### Query (cached) +A query is a data structure that caches its results. Queries are heavier to create since they need to build their cache, but are very fast to iterate. Queries cannot be created ad-hoc as they mutate the state of the world, but they can be iterated simultaneously from multiple threads. + +The performance of iterating a query is to simply iterate its cached list of archetypes. The actual matching is performed when the query is created, and afterwards, when archetypes are created or deleted. + +In pseudo code, query evaluation roughly looks like this: + +```python +for archetype in query.cache.archetypes: + yield archetype; +``` + +Just like with filters, an application then iterates the archetype, which can contain 1...N entities. + +Queries are almost a strict superset of filters, in that they provide the same functionality, but cached. There are a few exceptions to this rule, as there are some filters that cannot be cached. + +## Query creation +Queries can be created in different ways in flecs, which are described here. + +### Query DSL +The query DSL (domain specific language) is a flecs-specific string-based format that can be parsed at runtime into a valid query object. The query DSL is useful when an application cannot know in advance what a query will be, such as when exposing a query interface through a REST endpoint. + +This is an example of a simple query in the query DSL: +``` +Position, Velocity, NPC, (Likes, Apples) +``` + +For identifiers to be used in the query DSL they must be registered as named entities. + +Another feature of the query DSL is that it can be used in reverse, as a data definition format. This shows how the query DSL can be used to define an entity called "MyEntity" with components Position and Velocity: + +``` +Position(MyEntity) +Velocity(MyEntity) +``` + +The DSL relies on the optional parser addon. If the parser addon is not enabled, DSL strings cannot be parsed. + +### Simple queries (C++) +Queries that are just simple lists of components can be constructed by simply providing a list of types to the query factory function: + +```c +// q will be of type flecs::query<Position, const Velocity> +auto q = world.query<Position, const Velocity>(); +``` + +### Query Builder (C++) +The query builder is a C++ API to construct queries that follows the fluent API pattern. The query builder can be used to create either fully or partially constructed queries. + +This is an example of a simple query in the builder API: + +```cpp +// q will be of type flecs::query<Position, const Velocity> +auto q = world.query_builder<Position, const Velocity>() + .term<NPC>() + .term(Likes, Apples) + .build(); +``` + +The builder API allows for partially constructing (and then finalizing) queries: + +```cpp +auto qb = world.query_builder<Position, const Velocity>() + .term<NPC>(); + +if (add_fruit) { + qb.term(Likes, Apples); +} + +auto q = qb.build(); +``` + +The builder API has support for adding terms using the DSL: + +```cpp +auto q = world.query_builder<>("Position, [in] Velocity") + .term("NPC") + .term("(Likes, Apples)") + .build(); +``` + +### Query Descriptors (C) +The query descriptor is a C API to construct queries that follows the struct initialization pattern. Query descriptors can be used to create either fully or partially constructed queries. + +This is an example of the query descriptor API: + +```c +ecs_filter_t *f = ecs_filter_init(world, &(ecs_filter_desc_t){ + .terms = { + {ecs_id(Position)}, + {ecs_id(Velocity)}, + {NPC}, + {ecs_pair(Likes, Apples)} + } +}); +``` + +The descriptor API has support for using the DSL: + +```c +ecs_filter_t *f = ecs_filter_init(world, &(ecs_filter_desc_t){ + .terms = { + {ecs_id(Position)}, + {ecs_id(Velocity)} + }, + .expr = "NPC, (Likes, Apples)" +}); +``` + +Filters and queries can both be created with the descriptor API, and use the same descriptor structs. When creating a query, the `ecs_filter_desc_t` type is embedded by the `filter` member: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = { + {ecs_id(Position)}, + {ecs_id(Velocity)}, + {NPC}, + {ecs_pair(Likes, Apples)} + } +}); +``` + +## Query Iteration +This section describes the different ways in which a query can be iterated. + +### Each (C++) +The each function is a simple way to linearly iterate entities in a query. It is used by passing a (lambda) function to the `each` function. + +This is a simple example of using `each`: + +```cpp +// q will be of type flecs::query<Position, const Velocity> +auto q = world.query_builder<Position, const Velocity>() + .term<NPC>() + .build(); + +q.each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; +}); +``` + +Note that the arguments passed to each are derived from the query type. Terms added to the query by the query builder do not affect the query type, and do not appear in the argument list of the `each` function. + +### Iter (C++) +The iter function is a more advanced way to iterate entities in a query. It provides an application with more freedom to iterate a query, such as allowing for iterating the set of entities multiple times, or in different ordering. + +The iter callback is invoked per archetype, which means that it may be invoked multiple times per iter call. All the entities within a single call will be of the same type, meaning they have the exact same components. + +This is a simple example of using `iter`: + +```cpp +auto q = world.query_builder<Position, const Velocity>() + .term<NPC>() + .build(); + +q.iter([](flecs::iter& it, Position *p, const Velocity *v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +}); +``` + +Note how the iter function provides direct access to the component arrays. + +The iter callback can obtain access to components that are not part of the query type: + +```cpp +auto q = world.query_builder<Position, const Velocity>() + .term<Mass>() + .build(); + +q.iter([](flecs::iter& it, Position *p, const Velocity *v) { + auto mass = it.term<Mass>(3); // 3rd term of the query + + for (auto i : it) { + p[i].x += v[i].x / mass[i].value; + p[i].y += v[i].y / mass[i].value; + } +}); +``` + +Note how the `iter::term` function accepts a number. This number corresponds to the index of the term in the query, starting from 1. When a query is constructed with both template arguments and builder functions, the template arguments appear first in the query. If the type provided to the `it.term` function does not match with the component, the function will throw a runtime error. + +The iter callback an introspect the results of a query. When the type of a query term is not defined at creation time, which happens when using Or queries or wildcards, the type can be obtained in the iter callback: + +```cpp +auto q = world.query_builder<>() + .term(Likes, flecs::Wildcard) + .build(); + +q.iter([](flecs::iter& it) { + // Get the type id for the first term. + auto likes = it.term_id(1); + + // Extract the object from the pair + std::cout << "Entities like " + << likes.object().name() + << std::endl; +}); +``` + +Alternatively, the `iter` call can be written in such a way that it is fully generic, and just prints its inputs: + +```cpp +q.iter([](flecs::iter& it) { + for (int t = 0; t < it.term_count(); t++) { + auto id = it.term_id(t); + auto data = it.term(t); + + // Use id & data, for example for reflection + for (auto i : it) { + void *ptr = data[i]; + // ... + } + } +}); +``` + +### Iter (C) +The `ecs_query_iter` function is how C applications can iterate queries, and provides functionality similar to the C++ `iter` function. This is a simple example of using `ecs_query_iter`: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = { + {ecs_id(Position)}, + {ecs_id(Velocity)} + } +}); + +ecs_iter_t it = ecs_query_iter(q); +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} +``` + +Unlike C++, queries are not typed in C, and as such all components are obtained using the `ecs_term` function. Note how the number provided to `ecs_term` corresponds with the location of the component in the query, offset by 1. If a type is provided to `ecs_term` that does not match the term type, the function may throw a runtime error. + +Similar to the `term_id` function in C++, the C API has the `ecs_term_id` function: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = { + {ecs_pair(Likes, EcsWildcard)} + } +}); + +ecs_iter_t it = ecs_query_iter(q); +while (ecs_query_next(&it)) { + ecs_id_t id = ecs_term_id(&it, 1); + printf("Entities like %s\n", + ecs_get_name(world, ecs_get_object(world, id))); +} +``` + +## Query Concepts +Now that we have the basics under our belt, lets look a bit more in-depth at the different concepts from which queries are composed. + +### Expression +An expression refers to all the terms of the query. For a query that finds all entities with components Position & Velocity, the expression is `Position, Velocity`. An expression can be written down in any of the query description formats (DSL, builder or descriptor). However, when the term expression is used in the API, it typically indicates a DSL string. + +### Term +A term is a single element of a query expression. The query expression `Position, Velocity` has two terms, `Position` and `Velocity`. For a query to match an entity (or archetype), all terms in the expression must match. + +In the DSL, each term in an expression is usually separated by a comma, also called the `And` operator. + +### Identifier +A query term may contain one or more string-based identifiers that refer to the different entities in the term, such as `"Position"` and `"Velocity"`. These identifiers are used to lookup their corresponding entities while the query is constructed. Identifiers can be hierarchical, such as `flecs.components.transform.Position`. + +### Operator +An operator specifies how the term should be applied to the query. For example, if the result of a term is `true`, but it has the `Not` operator, the result of the term in the query will be `false`. Queries support the following operators: + +#### And operator +This is the default operator. Here is a simple example of a query with an And operator: + +```c +Position, Velocity // Position And Velocity +``` + +Terms added by the builder or descriptor by default use the `And` operator. + +#### Not operator +The Not operator instructs a query to reverse the result of a term. In the query DSL, the Not operator is specified after the And operator: + +```c +Position, !Velocity // Position And Not Velocity +``` + +This example shows how to add a `Not` term with the query builder: + +```cpp +auto qb = world.query_builder<Position, const Velocity>() + .term<NPC>().oper(flecs::Not); +``` + +This example shows how to add a `Not` term with the query descriptor: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)}, + {NPC, .oper = EcsNot} + } +}); +``` + +#### Optional operator +The optional operator instructs a query to return the result of the term as `true`, even if the entity/archetype did not match the term. In the query DSL the optional operator is provided after the `And` operator: + +```c +Position, ?Velocity // Position And Optionally Velocity +``` + +Optional arguments, while they do not impact query matching, are useful as they provide a quicker way to access the optional component than using `e.get<T>()`. Optional components are faster because of three reasons: + +1) Queries iterate archetypes, and if an archetype does not have the optional component, none of the entities in the archetype do. + +2) When an archetype does have the optional component, the query can access it as an array, just like a regular component. + +3) Cached queries store the archetype location of an optional component in their cache, which prevents the lookup that is needed by `get<T>()`. + +This example shows how to add a `Optional` term with the query builder: + +```cpp +auto qb = world.query_builder<Position, const Velocity>() + .term<NPC>().oper(flecs::Optional); +``` + +This example shows how to add a `Optional` term with the query descriptor: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)}, + {NPC, .oper = EcsOptional} + } +}); +``` + +#### Or operator +The Or operator instructs a query to match _at least_ one term out of a list of terms that are chained together by the operator. Here is a simple example of a query with an `Or` operator: + +```c +Position || Velocity // Position Or Velocity +``` + +When a query contains multiple `Or` chains separated by `And` operators, the entity must at least match one component from each chain: + +```c +// (Position Or Mass) And (Speed Or Velocity) +Position || Mass, Speed || Velocity +``` + +This example shows how to add a `Or` term with the query builder: + +```cpp +auto qb = world.query_builder<Position, const Velocity>() + .term<NPC>().oper(flecs::Or) + .term<Enemy>().oper(flecs::Or); +``` + +This example shows how to add a `Or` term with the query descriptor: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)}, + {NPC, .oper = EcsOr}, + {Enemy, .oper = EcsOr} + } +}); +``` + +Note that both in the builder and descriptor APIs, each term that participates in an `Or` chain must specify the `Or` operator. + +#### AndFrom, OrFrom, NotFrom operators +The *From operators allow a query to match against an external list of components with the `And`, `Or` or `Not` operator. This list of components is specified as a type entity. For example, if an application has the following type entity: + +```cpp +auto Ingredients = world.type("Ingredients") + .add(Bacon) + .add(Eggs) + .add(Tomato) + .add(Lettuce); +``` + +A term can select one of the ids in the type with the `OrFrom` operator, which in the query DSL looks like this: + +```c +OR | Ingredients // Match with at least one of Ingredients +``` + +Similarly, `AND`, and `NOT` can be used in combination with type entities. + +This example shows how to add an `AndFrom` term with the query builder: + +```cpp +auto qb = world.query_builder<Position, const Velocity>() + .term(Ingredients).oper(flecs::AndFrom); +``` + +This example shows how to add a `AndFrom` term with the query descriptor: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)}, + {Ingredients, .oper = EcsAndFrom} + } +}); +``` + +### Read/write access +A query term can specify whether the query intends to read or write data from the term. This is an example of a query term that specifies readonly access: + +``` +Position, [in] Velocity +``` + +This example shows how to specify read/write access in the builder API: + +```cpp +auto qb = world.query_builder<Position>() + .term<Velocity>().inout(flecs::In); +``` + +Alternatively a query may also use `const` to indicate readonly access, which is equivalent to specifying `flecs::In`. Using `const` however should be preferred, as the qualifier is propagated to the `each` and `iter` callbacks: + +```cpp +auto q = world.query<Position, const Velocity>(); + +q.each([](flecs::entity e, Position& p, const Velocity& v){ + p.x += v.x; + p.y += v.y; +}); +``` + +This example shows how to specify read/write access in the query descriptor: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)}, + {ecs_id(Position), .inout = EcsIn} + } +}); +``` + +The default read/write access is `InOut` for terms that have `This` as the subject. Terms that have an entity other than `This` have `In` as default. Either defaults can be overridden. + +### Predicate +Each term has exactly one predicate. Conceptually, a predicate is a function that returns true when its inputs match, and false when its inputs do not match. When translating this to simple flecs query, a component represents a predicate that returns true when its input has the component: + +```c +Position(Bob) // true if entity Bob has Position +``` + +For a term to match, its predicate has to evaluate to true. + +### Subject +A subject is the argument passed to a predicate. In most cases this is the entity (or archetype) that is being matched with the query, but not necessarily. Take for example this term: + +``` +Position(Bob) +``` + +Here `Position` is the predicate, and `Bob` is the subject. If `Bob` has `Position`, the predicate will return true and the term will match. Otherwise the term will return false. + +The term "subject" is borrowed from English grammar. In the sentence "Bob has component Position", "Bob" is the subject. + +### This +"This" is the placeholder for an entity (or archetype) being evaluated by a query. When a query is looking for all matching results, it will iterate the set of entities that represent a potential match, and pass each entity (or archetype) as "This" to each term. By default "This" is used as the subject for each predicate. Thus a simple query like this: + +``` +Position, Velocity +``` + +actually looks like this when written out with explicit subjects: + +``` +Position(This), Velocity(This) +``` + +### Object +An object is an optional second argument that can be passed to a predicate. Objects are used when querying for relationships. For example, entity `Bob` may have the relationship `Likes` with `Alice`. In this case, `Bob` is the subject, `Likes` is the predicate and `Alice` is the object. When querying for whether `Bob` likes `Alice`, we do: + +```c +Likes(Bob, Alice) // True when Bob Likes Alice +``` + +When we are querying for a relationship on `This`, we can use this shorthand notation: + +```c +(Likes, Alice) +``` + +which is the same as: + +```c +Likes(This, Alice) +``` + +The term "object" is borrowed from English grammar. In the sentence "Bob likes Alice", "Bob" is the subject, and "Alice" is the object. + +### Component +When a term refers to a component, it appears as a predicate with a single argument (subject). Components are typically referred to by their language types in APIs. While the query DSL is agnostic to this, the bindings provide dedicated ways for setting a component for a term. + +In the builder API components can be specified using template arguments: + +```cpp +// Position & Velocity are C++ types +auto qb = world.query_builder<Position, const Velocity>() + .term<Mass>(); // Mass is a C++ type +``` + +In the descriptor API components can be specified with the `ecs_id` macro. This macro requires that the component id is known in the current scope (see the manual for how to register components/declare component identifiers): + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)}, + {ecs_id(Velocity)}, + {ecs_id(Mass)} + } +}); +``` + +### Tag +A tag is similar to a component when used in a query, in that it appears as a predicate with a single argument (subject). Tags are regular entities that are not associated with a datatype. + +In the builder API tags can be added like this: + +```cpp +// NPC +auto NPC = world.entity(); +auto qb = world.query_builder<>() + .term(NPC); +``` + +In the query descriptor API tags can be added like this: + +```c +// NPC +ecs_entity_t NPC = ecs_new_id(world); +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {NPC} + } +}); +``` + +Note that in C++ empty types can be used as tags. These can be specified in the same way as a regular component, by their type. + +### Relation +A relation in a term is a predicate with two arguments, a subject and an object. Relations and objects can be specified as either entities or types in the builder API, depending on how the application defines them: + +```cpp +// (Likes, Apples) +auto qb = world.query_builder<>() + .term<Likes, Apples>(); + +auto qb = world.query_builder<>() + .term(Likes, Apples); + +auto qb = world.query_builder<>() + .term<Likes>(Apples); +``` + +In the query descriptor API relations can be specified as pairs: + +```c +// (Likes, Apples) +ecs_entity_t NPC = ecs_new_id(world); +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_pair(Likes, Apples)} + } +}); +``` + +Just like in C++, pairs can contain either types or entities: + +```c +// (Likes, Apples) +ecs_entity_t NPC = ecs_new_id(world); +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_pair(ecs_id(Likes), ecs_id(Apples))} + } +}); +``` + +### Singleton +A singleton in Flecs is a component or tag that has been added to itself. In the query langauge this can be written down as a term with a predicate that references itself as the subject: + +``` +Game(Game) +``` + +Since this is such a common pattern, the query DSL provides a shortcut for singletons with the singleton operator (`$`). This term is an example of how it is used, and is equivalent to `Game(Game)`: + +``` +$Game +``` + +This example shows how to add a singleton with the query builder: + +```cpp +// $Game +auto qb = world.query_builder<Position, const Velocity>() + .term<Game>().singleton(); +``` + +This example shows how to add a singleton with the query descriptor. Note that the descriptor does not have a dedicated API for singletons: + +```c +// Game(Game) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Game), .args[0].entity = ecs_id(Game)} + } +}); +``` + +### Assigning Identifiers with the API +The query builder and descriptor APIs allow for setting the individual predicate, subject and object of a term. These are also referred to as the "term identifiers". + +This example shows how to explicitly set identifiers with the builder API: + +```cpp +// Position(This), (Likes, Alice) +auto qb = world.query_builder<>() + .term<Position>().subject(flecs::This) + .term(Likes).object(Alice); +``` + +This example shows how to explicitly set identifiers with the query descriptor: + +```c +// Position(This), (Likes, Alice) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {.predicate.entity = ecs_id(Position), .args[0].entity = EcsThis}, + {.predicate.entity = Likes, .args[1].entity = Alice} + } +}); +``` + +Note how in the descriptor API, the subject and object are assigned as elements in an arguments array for the predicate. + +In practice it is often enough to just assign the predicate, and an optional object for terms that query for a relation, as by default the subject is set to `EcsThis`. The predicate and object can both be set in a single `id` field. In the descriptor API, the `id` field is the first member of the struct, so it is possible to do this: + +```c +// Position +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position)} + } +}); +``` + +which is equivalent to + +```c +// Position(This) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {.predicate = ecs_id(Position), .args[0].entity = EcsThis} + } +}); +``` + +The id can also contain an additional object: + +```c +// (Likes, Alice) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_pair(Likes, Alice)} + } +}); +``` + +which is equivalent to + +```c +// Likes(This, Alice) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + { + .predicate = Likes, + .args[0].entity = EcsThis, + .args[1].entity = Alice + } + } +}); +``` + +### Wildcard +We can use the wildcard to match with any entity. Wildcards are typically used in combination with relationships. This is a simple example of a term that returns all entities (objects) that `This` likes. + +``` +(Likes, *) +``` + +A single entity may have multiple `Likes` relationships. When this is the case, the above term will cause the same entity to be returned multiple times, once for each object of the `Likes` relationship. + +Wildcards can also be used as predicate. This term returns all entities that have a relationship with Alice: + +``` +(*, Alice) +``` + +It is also possible to query for all relationships for an entity: + +``` +(*, *) +``` + +Wildcards are not limited to relationships. The following query is also valid, and requests all components from the iterated over entity: + +``` +* +``` + +Note that `(*, *)` only matches relations, and `*` only matches components. To create a query that matches everything from an entity, the two wildcard expressions have to be combined with an `Or` expression: + +``` +(*, *) || * +``` + +This example shows how to use wildcards in the C++ API: + +```cpp +// (Likes, *) +auto qb = world.query_builder<>() + .term(Likes, flecs::Wildcard); +``` + +This example shows how to use wildcards in the query descriptor: + +```c +// (Likes, *) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_pair(Likes, EcsWildcard)} + } +}); +``` + +### Variables +A variable is a wildcard that must match with the same entity across terms. To illustrate where this is useful, let's first start with a query that finds all the `Likes` relationships for entities that have them: + +``` +(Likes, *) +``` + +Now suppose we want to know whether the object matched by the wildcard (`*`) is also a colleague of the subject. We could do this: + +``` +(Likes, *), (Colleague, *) +``` + +But that does not work, as this query would return all Likes relationships * all Colleague relationships. To constrain the query to only return `Likes` relationships for objects that are also colleagues, we can use a variable: + +``` +(Likes, X), (Colleague, X) +``` + +A variable is an identifier that is local to the query. It does not need to be defined in advance. By default identifiers that are all uppercase are automatically parsed as a variable by the DSL. + +Variables can occur in multiple places. For example, this query returns all the relationships the `This` entity has with each object it likes as `R`: + +``` +(Likes, X), (R, X) +``` + +A useful application for variables is ensuring that an entity has a component referenced by a relationship. Consider an application that has an `ExpiryTimer` relationship that removes a component after a certain time has expired. This logic only needs to be executed when the entity actually has the component to remove. + +With variables this can be ensured: + +``` +(ExpiryTimer, C), C +``` + +Variables allow queries to arbitrarily traverse the entity relationship graph. For example, the following query tests whether there exist entities that like objects that like their enemies: + +``` +(Likes, FRIEND), Likes(FRIEND, ENEMY), (Enemy, ENEMY) +``` + +This example shows how to use variables in the C++ API: + +```cpp +// (Likes, X), (Colleague, X) +auto qb = world.query_builder<>() + .term(Likes).object("X") + .term(Colleague).object("X"); +``` + +This example shows how to use wildcards in the query descriptor: + +```c +// (Likes, X), (Colleague, X) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {.predicate.entity = Likes, .args[1].name = "X"} + {.predicate.entity = Colleague, .args[1].name = "X"} + } +}); +``` + +**NOTE**: Variables are not yet universally supported yet in cached queries, as not all expressions yield cacheable archetypes. Near future versions of Flecs will have basic variable support for cached queries where variables cannot be used as subject, but are valid in other places. Full caching support for any expression with variables will likely not arrive before the next major release. + +### Set Substitution +The different parts of a query term (predicates, subjects, objects) can be automatically substituted by following one of their relationships. While this may sound complicated, it is actually pretty common, and quite useful. + +A typical example of a use case that requires substitution is, "find component `Position` for a parent entity". To achieve this, we need to tell the query term which relationship to follow to reach the parent. In this case that is the builtin `ChildOf` relationship. + +We also need to tell the query in which direction to follow the relationship. We can either follow the relationship upwards, which gives us parents, or downwards, which gives us children of the substituted entity. + +The following term shows how to write the above example down in the DSL: + +``` +Position(superset(ChildOf)) +``` + +Let's unpack what is happening here. First of all the term has a regular `Position` predicate. The subject of this term is `superset(ChildOf)`. What this does is, it instructs the term to search upwards (`superset`) for the `ChildOf` relationship. + +As a result, this term will follow the `ChildOf` relation of the `This` subject until it has found an object with `Position`. Here is the behavior in pseudo code: + +```python +def find_object_w_component(This, Component): + for pair in This.each(ChildOf, *): + if pair.object.has(Component): + yield pair.object + else + find_object_w_component(pair.object, Component) + return 0 '// No match +``` + +#### Inclusive Substitution +Substitution can do more than just searching supersets. It is for example possible to start the search on `This` itself, and when the component is not found on `This`, keep searching by following the `ChildOf` relation: + +```c +Position(self|superset(ChildOf)) +``` + +A substitution that has both `self` and `superset` or `subset` is also referred to as an "inclusive" substitution. + +This behavior is equivalent to this almost identical pseudo code: + +```python +def find_object_w_component(This, Component): + if This.has(Component): + yield This + + for pair in This.each(ChildOf, *): + find_object_w_component(pair.object, Component) + + return 0 '// No match +``` + +#### Substitution Depth +Queries can specify how deep the query should search. For example, the following term specifies to search the `ChildOf` relation, but no more than 3 levels deep: + +```c +Position(superset(ChildOf, 3)) +``` + +Additionally, it is also possible to specify a minimum search depth: + +```c +// Start at depth 2, search until at most depth 4 +Position(superset(ChildOf, 2, 4)) +``` + +#### Cascade Ordering +Substitution expressions may contain the `cascade` modifier, which ensures that results of the query are iterated in breadth-first order, where depth is defined by the relation used int the substitution. + +A useful application of `cascade` is transform systems, where parents need to be transformed before their children. The term in the following example finds the `Transform` component from both `This` and its parent, while ordering the results of the query breadth-first: + +``` +Transform, Transform(cascade|superset(ChildOf)) +``` + +In an actual transform system we would also want to match the root, which can be achieved by making the second term optional: + +``` +Transform, ?Transform(cascade|superset(ChildOf)) +``` + +#### Substitute for All +The default behavior of a substitution term is to stop looking when an object with the required component has been found. The following example shows a term that specifies that the substitution needs to keep looking, so that the entire tree (upwards or downwards) for a subject is returned: + +``` +Transform(all|superset(ChildOf)) +``` + +#### Substitution on Identifiers +So far all the substitution terms have applied to a default (`This`) subject. Substitution can also be defined on entities other than `This`: + +```c +// Get Position for a parent of Bob +Position(Bob[superset(ChildOf)]) +``` + +Additionally, substitution is not limited to the subject: + +```c +// Does the entity like any of the parents of Alice? +(Likes, Alice[superset(ChildOf)]) +``` + +This example shows how to use substitution in the C++ API: + +```cpp +// Position(superset(ChildOf)) +auto qb = world.query_builder<>() + .term<Position>().superset(ChildOf); + +// Position(self|superset(ChildOf, 3)) +auto qb = world.query_builder<>() + .term<Position>() + .set(flecs::Self | flecs::SuperSet, flecs::ChildOf) + .max_depth(3); +``` + +This example shows how to use wildcards in the query descriptor: + +```c +// Position(superset(ChildOf)) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position), .args[0].set = { + .mask = EcsSuperSet, + .relation = EcsChildOf + }} + } +}); + +// Position(self|superset(ChildOf, 3)) +ecs_query_t *q = ecs_query_init(world, &(ecs_query_decs_t){ + .filter.terms = { + {ecs_id(Position), .args[0].set = { + .mask = EcsSelf | EcsSuperSet, + .relation = EcsChildOf, + .max_depth = 3 + }} + } +}); +``` + +**NOTE**: While substitution can require recursive algorithms, cached queries do not perform this logic when iterating results. Matching for cached queries is only performed when queries are created, and afterwards, when new archetypes are created. As a result, cached queries can have complex expressions without visible impact on iteration performance. + +**NOTE**: Subset substitution rules are not yet supported for cached queries. Terms with `all` are not supported yet in cached queries. + +## Transitivity +Queries can work with transitive relations. A relation `R` is transitive if the following is true: + +```basic +if R(X, Y) and R(Y, Z) then R(X, Z) +``` + +A common example of transitivity is inheritance. Consider, if `GrannySmith` is an `Apple`, and an `Apple` is `Fruit`, then a `GrannySmith` is also `Fruit`. + +Suppose an application keeps track of the location of entities. Entities can be located in a neighborhood, which are part of a city, which are part of a country. We could create a `LocatedIn` relation with a few locations like this: + +```cpp +// Create the LocatedIn relation +auto LocatedIn = world.entity(); + +// Create locations & their relations to each other +auto US = world.entity(); +auto SanFrancisco = world.entity().add(LocatedIn, US); +auto Mission = world.entity().add(LocatedIn, SanFrancisco); +auto SOMA = world.entity().add(LocatedIn, SanFrancisco); + +// An entity located in the Mission +auto e = world.entity().add(LocatedIn, Mission); +``` + +We could now create the following query: +```c +(LocatedIn, SanFrancisco) +``` + +At the moment this query will not match with the entity, because the entity itself does not have `(LocatedIn, SanFrancisco)`. We can however change this behavior by making the `LocatedIn` relation transitive: + +```cpp +auto LocatedIn = world.entity().add(flecs::Transitive); +``` + +This tells the query that it should treat an entity that has `(LocatedIn, Mission)` as an entity that has `(LocatedIn, SanFrancisco)`, because `Mission` has `(LocatedIn, SanFrancisco)`. We can now also run this query: + +``` +(LocatedIn, US) +``` + +which will also return the entity, as it has `(LocatedIn, Mission)`, `Mission` has `(LocatedIn, SanFrancisco)`, and `SanFrancisco` has `(LocatedIn, US)`. + +### Transitivity and Substitution +To understand how transitivity is implemented, we need to look at how queries interpret a transitive relation. When a query encounters a transitive relation, it inserts a substitution term for that relation. Consider the previous example with a transitive `LocatedIn` relation: + +``` +(LocatedIn, SanFrancisco) +``` + +To find whether the subject should match this term, it should not just consider `(LocatedIn, SanFrancisco)`, but also `(LocatedIn, Mision)` and `(LocatedIn, SOMA)`. To achieve this, a query will insert an implicit substitution on the object, when the relation is transitive: + +``` +(LocatedIn, SanFrancisco[self|subset(LocatedIn)]) +``` + +Note that the substitution includes `self`, as we also should match entities that are in `SanFrancisco` itself. + +### Transitivity and Cycles +Transitive relations are not allows to have cycles. While neither queries nor the storage check for cycles (doing so would be too expensive), adding a cycle with a transitive relation can cause infinite recursion when evaluating the relation with a query. + +## The IsA relation +The `IsA` relation is a builtin flecs relation that allows applications to define that an object is a subset of another object. Consider an application that categorizes artworks. It could create the following `IsA` hierarchy: + +```cpp +auto Artwork = world.entity(); +auto Painting = world.entity().add(flecs::IsA, Artwork); +auto Portrait = world.entity().add(flecs::IsA, Painting); +auto SelfPortrait = world.entity().add(flecs::IsA, Portrait); +``` + +Alternatively, it could use the shorthand `is_a`: function + +```cpp +auto Artwork = world.entity(); +auto Painting = world.entity().is_a(Artwork); +auto Portrait = world.entity().is_a(Painting); +auto SelfPortrait = world.entity().is_a(Portrait); +``` + +We could now instantiate artworks with these "classes": + +```cpp +auto MonaLisa = world.entity().add(Portrait); +``` + +The `IsA` relation is transitive. This means that adding `Portrait` to `MonaLisa`, also means that the entity should be treated _as if_ it also had `Artwork` and `Painting`. + +### The IsA relation and Queries +Queries do automatic `IsA` substitution on the predicate, subject and object. To see how this works exactly, let's use the previous example. A query term could ask for all entities that are an artwork: + +``` +Artwork +``` + +Ordinarily this would only match entities that have `Artwork`, but because there are entities with an `IsA` relation to `Artwork`, this query should expand to: + +``` +Artwork +Painting +Portrait +SelfPortrait +``` + +To achieve this, a query implicitly substitutes terms with their `IsA` subsets. When written out in full, this looks like: + +``` +Artwork[self|all|subset(IsA)] +``` + +The default relation for set substitution is `IsA`, so we can rewrite this as a slightly shorter term: + +``` +Artwork[self|all|subset] +``` + +Note the `all` modifier. We need to consider all subsets, not just the subsets until a result has been found, to find all entities that are artworks. + +The predicate is not the only part of a query for which implicit `IsA` substitution happens. Imagine we use this query to find all things that are an artwork: + +``` +(IsA, Artwork) +``` + +Instead of storing the actual artworks (the `MonaLisa`) this query returns all the things that _are_ an artwork: `Painting`, `Portrait` and `SelfPortrait`. To achieve this, the query needs to substitute the object. When written out in full, this looks like: + +``` +(IsA, Artwork[self|all|subset]) +``` + +This effectively expands the query to: + +``` +(IsA, Artwork) +(IsA, Painting) +(IsA, Portrait) +(IsA, SelfPortrait) +``` + +Finally, the subject itself can also be automatically substituted. This is quite common for applications that use the `IsA` relation to construct prefabs. Consider the following code that creates a spaceship prefab: + +```cpp +auto SpaceShip = world.prefab() + .set<Attack>({100}) + .set<Defense>({50}) + .set<MaxSpeed>({75}); +``` + +We can now use this prefab to create an entity that shares its components with the `IsA` relation: + +```cpp +auto my_spaceship = world.entity() + .is_a(SpaceShip) + .set<Position>({10, 20}); + .set<Velocity>({5, 5}); +``` + +Suppose we now want to find all entities that exceed their maximum speed. We could use the following query: + +``` +MaxSpeed, Velocity +``` + +However, `my_spaceship` only has `MaxSpeed` through `SpaceShip`, which this query will not find unless it substitutes the subject. When written out, this substitution looks like this: + +``` +MaxSpeed(self|superset), Velocity(self|superset) +``` + +When all implicit substitution is written out in full, a single component query looks like this: + +``` +Component[self|all|subset] (self|superset) +``` + +**NOTE**: cached queries currently do not support implicit substitution on predicates and objects. Implicit `IsA` substitution on subjects needs to be enabled explicitly, by specifying `subset` or `self|subset` as the subject. + +This behavior may change in future versions, as more efficient methods become available to find the set of `IsA` subsets or supersets for an entity. + +### Final Entities +An application can mark an entity as `Final`, which means that it cannot have `IsA` subsets. This is similar to the meaning of `Final` in class-based inheritance, where a final class cannot be derived from. + +A query will not do implicit `IsA` substitution for predicates if they are final. When a component is registered, it is made `Final` by default, which means that for regular component queries a query will never attempt to substitute the predicate. Additionally, the algorithm that searches for `IsA` subsets may run more efficient when a `Final` subset is found. + +To mark an entity as final, an application can add the `Final` tag: + +```cpp +// Don't try to substitute "Likes" in queries +auto Likes = world.entity().add(flecs::Final); +``` diff --git a/fggl/ecs2/flecs/docs/Quickstart.md b/fggl/ecs2/flecs/docs/Quickstart.md new file mode 100644 index 0000000000000000000000000000000000000000..bb2666726b035e817c2f5b931e7e24a12866854e --- /dev/null +++ b/fggl/ecs2/flecs/docs/Quickstart.md @@ -0,0 +1,802 @@ +# Flecs Quickstart +This document provides a quick overview of the different features and concepts in Flecs with short examples. This is a good resource if you're just getting started or just want to get a better idea of what kind of features are available in Flecs! + +## Overview +This shows an overview of all the different concepts in Flecs and how they wire together. The sections in the quickstart go over them in more detail and with code examples. + + + +## World +The world is the container for all ECS data. It stores the entities and their components, does queries and runs systems. Typically there is only a single world, but there is no limit on the number of worlds an application can create. + +```c +ecs_world_t *world = ecs_init(); + +// Do the ECS stuff + +ecs_fini(world); +``` +```cpp +flecs::world world; + +// Do the ECS stuff +``` + +## Entity +An entity is a unique thing in the world, and is represented by a 64 bit id. Entities can be created and deleted. If an entity is deleted it is no longer considered "alive". A world can contain up to 4 billion(!) alive entities. Entity identifiers contain a few bits that make it possible to check whether an entity is alive or not. + +```c +ecs_entity_t e = ecs_new_id(world); +ecs_is_alive(world, e); // true! + +ecs_delete(world, e); +ecs_is_alive(world, e); // false! +``` +```cpp +auto e = world.entity(); +e.is_alive(); // true! + +e.destruct(); +e.is_alive(); // false! +``` + +Entities can have names which makes it easier to identify them in an application. In C++ the name can be passed to the constructor. In C a name can be assigned with the `ecs_entity_init` function. If a name is provided during entity creation time and an entity with that name already exists, the existing entity will be returned. + +```c +ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t) { .name = "Bob" }); + +printf("Entity name: %s\n", ecs_get_name(world, e)); +``` +```cpp +auto e = world.entity("Bob"); + +std::cout << "Entity name: " << e.name() << std::endl; +``` + +Entities can be looked up by name with the `lookup` function: + +```c +ecs_entity_t e = ecs_lookup(world, "Bob"); +``` +```cpp +auto e = world.lookup("Bob"); +``` + +## Id +An id is a 64 bit number that can encode anything that can be added to an entity. In flecs this can be either a component, tag or a pair. A component is data that can be added to an entity. A tag is an "empty" component. A pair is a combination of two component/tag ids which is used to encode entity relationships. All entity/component/tag identifiers are valid ids, but not all ids are valid entity identifier. + +The following sections describe components, tags and pairs in more detail. + +## Component +A component is a type of which instances can be added and removed to entities. Each component can added only once to an entity (though not really, see [Pair](#pair)). In C applications components must be registered before use. In C++ this happens automatically. + +```c +ECS_COMPONENT(world, Position); +ECS_COMPONENT(world, Velocity); + +ecs_entity_t e = ecs_new_id(world); + +// Add a component. This creates the component in the ECS storage, but does not +// assign it with a value. +ecs_add(world, e, Velocity); + +// Set the value for the Position & Velocity components. A component will be +// added if the entity doesn't have it yet. +ecs_set(world, e, Position, {10, 20}); +ecs_set(world, e, Velocity, {1, 2}); + +// Get a component +const Position *p = ecs_get(world, e, Position); + +// Remove component +ecs_remove(world, e, Position); +``` +```cpp +auto e = world.entity(); + +// Add a component. This creates the component in the ECS storage, but does not +// assign it with a value. +e.add<Velocity>(); + +// Set the value for the Position & Velocity components. A component will be +// added if the entity doesn't have it yet. +auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + +// Get a component +const Position *p = e.get<Position>(); + +// Remove component +e.remove<Position>(); +``` + +Each component is associated by a unique entity identifier by Flecs. This makes it possible to inspect component data, or attach your own data to components. C applications can use the `ecs_id` macro to get the entity id for a component. C++ applications can use the `world::id` function: + +```c +ECS_COMPONENT(world, Position); + +ecs_entity_t pos_e = ecs_id(Position); +printf("Name: %s\n", ecs_get_name(world, pos_e)); // outputs 'Name: Position' + +// It's possible to add components like you would for any entity +ecs_add(world, pos_e, Serializable); +``` +```cpp +flecs::entity pos_e = world.id<Position>(); +std::cout << "Name: " << pos_e.name() << std::endl; // outputs 'Name: Position' + +// It's possible to add components like you would for any entity +pos_e.add<Serializable>(); +``` + +The thing that makes an ordinary entity a component is the `EcsComponent` (or `flecs::Component`, in C++) component. This is a builtin component that tells Flecs how much space is needed to store a component, and can be inspected by applications: + +```c +ECS_COMPONENT(world, Position); + +ecs_entity_t pos_e = ecs_id(Position); + +const EcsComponent *c = ecs_get(world, pos_e, EcsComponent); +printf("Component size: %u\n", c->size); +``` +```cpp +flecs::entity pos_e = world.id<Position>(); + +const EcsComponent *c = pos_e.get<flecs::Component>(); +std::cout << "Component size: " << c->size << std::endl; +``` + +Because components are stored as regular entities, they can in theory also be deleted. To prevent unexpected accidents however, by default components are registered with a tag that prevents them from being deleted. If this tag were to be removed, deleting a component would cause it to be removed from all entities. For more information on these policies, see [Relation cleanup properties](Relations.md#relation_cleanup_properties). + +## Tag +A tag is a component that does not have any data. In Flecs tags can be either empty types (in C++) or regular entities (C & C++) that do not have the `EcsComponent` component (or have an `EcsComponent` component with size 0). Tags can be added & removed using the same APIs as adding & removing components, but because tags have no data, they cannot be assigned a value. Because tags (like components) are regular entities, they can be created & deleted at runtime. + +```c +// Create Enemy tag +ecs_entity_t Enemy = ecs_new_id(world); + +// Create entity, add Enemy tag +ecs_entity_t e = ecs_new_id(world); + +ecs_add_id(world, e, Enemy); +ecs_has_id(world, e, Enemy); // true! + +ecs_remove_id(world, e, Enemy); +ecs_has_id(world, e, Enemy); // false! +``` +```cpp +// Option 1: create Tag as empty struct +struct Enemy { }; + +// Create entity, add Enemy tag +auto e = world.entity().add<Enemy>(); +e.has<Enemy>(); // true! + +e.remove<Enemy>(); +e.has<Enemy>(); // false! + + +// Option 2: create Tag as entity +auto Enemy = world.entity(); + +// Create entity, add Enemy tag +auto e = world.entity().add(Enemy); +e.has(Enemy); // true! + +e.remove(Enemy); +e.has(Enemy); // false! +``` + +Note that both options in the C++ example achieve the same effect. The only difference is that in option 1 the tag is fixed at compile time, whereas in option 2 the tag can be created dynamically at runtime. + +When a tag is deleted, the same rules apply as for components (see [Relation cleanup properties](Relations.md#relation_cleanup_properties)). + +## Pair +A pair is a combination of two entity ids. Pairs can be used to store entity relations, where the first id represents the relation kind and the second id represents the relation target (called "object"). This is best explained by an example: + +```c +// Create Likes relation +ecs_entity_t Likes = ecs_new_id(world); + +// Create a small graph with two entities that like each other +ecs_entity_t Bob = ecs_new_id(world); +ecs_entity_t Alice = ecs_new_id(world); + +ecs_add_pair(world, Bob, Likes, Alice); // Bob likes Alice +ecs_add_pair(world, Alice, Likes, Bob); // Alice likes Bob +ecs_has_pair(world, Bob, Likes, Alice); // true! + +ecs_remove_pair(world, Bob, Likes, Alice); +ecs_has_pair(world, Bob, Likes, Alice); // false! +``` +```cpp +// Create Likes relation as empty type (tag) +struct Likes { }; + +// Create a small graph with two entities that like each other +auto Bob = world.entity(); +auto Alice = world.entity(); + +Bob.add<Likes>(Alice); // Bob likes Alice +Alice.add<Likes>(Bob); // Alice likes Bob +Bob.has<Likes>(Alice); // true! + +Bob.remove<Likes>(Alice); +Bob.has<Likes>(Alice); // false! +``` + +A pair can be encoded in a single 64 bit identifier by using the `ecs_pair` macro in C, or the `world.pair` function in C++: + +```c +ecs_id_t id = ecs_pair(Likes, Bob); +``` +```cpp +flecs::id id = world.pair(Likes, Bob); +``` + +The following examples show how to get back the relation and object pairs from a pair id: + +```c +if (ecs_id_is_pair(id)) { + ecs_entity_t rel = ecs_pair_relation(world, id); + ecs_entity_t obj = ecs_pair_object(world, id); +} +``` +```cpp +if (id.is_pair()) { + auto rel = id.relation(); + auto obj = id.object(); +} +``` + +A component or tag can be added multiple times to the same entity as long as it is part of a pair, and the pair itself is unique: + +```c +ecs_add_pair(world, Bob, Eats, Apples); +ecs_add_pair(world, Bob, Eats, Pears); +ecs_add_pair(world, Bob, Grows, Pears); + +ecs_has_pair(world, Bob, Eats, Apples); // true! +ecs_has_pair(world, Bob, Eats, Pears); // true! +ecs_has_pair(world, Bob, Grows, Pears); // true! +``` +```cpp +Bob.add(Eats, Apples); +Bob.add(Eats, Pears); +Bob.add(Grows, Pears); + +Bob.has(Eats, Apples); // true! +Bob.has(Eats, Pears); // true! +Bob.has(Grows, Pears); // true! +``` + +The `get_object` function can be used in C and C++ to get the object for a relation: + +```c +ecs_entity_t o = ecs_get_object(world, Alice, Likes, 0); // Returns Bob +``` +```cpp +auto o = Alice.get_object<Likes>(); // Returns Bob +``` + +Entity relations enable lots of interesting patterns and possibilities. Make sure to check out the [Relations manual](Relations.md). + +## Hierarchies +Flecs has builtin support for hierarchies with the builtin `EcsChildOf` (or `flecs::ChildOf`, in C++) relationship. A hierarchy can be created with the regular relationship API, or with the `child_of` shortcut in C++: + +```c +ecs_entity_t parent = ecs_new_id(world); + +// ecs_new_w_pair is the same as ecs_new_id + ecs_add_pair +ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + +// Deleting the parent also deletes its children +ecs_delete(world, parent); +``` +```cpp +auto parent = world.entity(); +auto child = world.entity().child_of(parent); + +// Deleting the parent also deletes its children +parent.destruct(); +``` + +When entities have names, they can be used together with hierarchies to generate path names or do relative lookups: + +```c +ecs_entity_t parent = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" +}); + +ecs_entity_t child = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child" +}); + +ecs_add_pair(world, child, EcsChildOf, parent); + +char *path = ecs_get_fullpath(world, child); +printf("%s\n", path); // output: 'parent.child' +ecs_os_free(path); + +ecs_lookup_path(world, 0, "parent.child"); // returns child +ecs_lookup_path(world, parent, "child"); // returns child +``` +```cpp +auto parent = world.entity("parent"); +auto child = world.entity("child").child_of(parent); +std::cout << child.path() << std::endl; // output: 'parent::child' + +world.lookup("parent::child"); // returns child +parent.lookup("child"); // returns child +``` + +Queries (see below) can use hierarchies to order data breadth-first, which can come in handy when you're implementing a transform system: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .terms = { + { ecs_id(Position) }, + { ecs_id(Position), .args[0].set = { + .mask = EcsCascade, // Force breadth-first order + .relation = EcsChildOf // Use ChildOf relation for ordering + }} + } +}); + +ecs_iter_t it = ecs_query_iter(q); +while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Position *p_parent = ecs_term(&it, Position, 2); + for (int i = 0; i < it.count; i ++) { + // Do the thing + } +} +``` +```cpp +auto q = world.query_builder<Position, Position>() + .arg(2).set(flecs::Cascade, flecs::ChildOf) + .build(); + +q.each([](Position& p, Position& p_parent) { + // Do the thing +}); +``` + +## Instancing +Flecs has builtin support for instancing (sharing a single component with multiple entities) through the builtin `EcsIsA` relation (`flecs::IsA` in C++). An entity with an `IsA` relation to a base entity "inherits" all entities from that base: + +```c +// Shortcut to create entity & set a component +ecs_entity_t base = ecs_set(world, 0, Triangle, {{0, 0}, {1, 1}, {-1, -1}}); + +// Create entity that shares components with base +ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); +const Triangle *t = ecs_get(world, e, Triangle); // gets Triangle from base +``` +```cpp +auto base = world.entity().set<Triangle>({{0, 0}, {1, 1}, {-1, -1}}); + +// Create entity that shares components with base +auto e = world.entity().is_a(base); +const Triangle *t = e.get<Triangle>(); // gets Triangle from base +``` + +Entities can override components from their base: + +```c +// Add private instance of Triangle to e, copy value from base +ecs_add(world, e, Triangle); +``` +```cpp +// Add private instance of Triangle to e, copy value from base +e.add<Triangle>(); +``` + +Instancing can be used to build modular prefab hierarchies, as the foundation of a batched renderer with instancing support, or just to reduce memory footprint by sharing common data across entities. + +## Type +The type (often referred to as "archetype") is the list of ids an entity has. Types can be used for introspection which is useful when debugging, or when for example building an entity editor. The most common thing to do with a type is to convert it to text and print it: + +```c +ECS_COMPONENT(world, Position); +ECS_COMPONENT(world, Velocity); + +ecs_entity_t e = ecs_new_id(world); +ecs_add(world, e, Position); +ecs_add(world, e, Velocity); + +ecs_type_t type = ecs_get_type(world, e); +char *type_str = ecs_type_str(world, type); +printf("Type: %s\n", type_str); // output: 'Position,Velocity' +ecs_os_free(type_str); +``` +```cpp +auto e = ecs.entity() + .add<Position>() + .add<Velocity>(); + +std::cout << e.type().str() << std::endl; // output: 'Position,Velocity' +``` + +A type can also be iterated by an application: +```c +ecs_type_t type = ecs_get_type(world, e); +ecs_id_t *ids = ecs_vector_first(type); +for (int i = 0; i < ecs_vector_count(type; i ++) { + if (ids[i] == ecs_id(Position)) { + // Found Position component! + } +} +``` +```cpp +e.each([&](flecs:id id) { + if (id == world.id<Position>()) { + // Found Position component! + } +} +``` + +## Singleton +A singleton is a single instance of a component that can be retrieved without an entity. The functions for singletons are very similar to the regular API: + +```c +// Set singleton component +ecs_set_singleton(world, Position, {10, 20}); + +// Get singleton component +const Position *p = ecs_get_singleton(world, Position); +``` +```cpp +// Set singleton component +world.set<Position>({10, 20}); + +// Get singleton component +const Position *p = world.get<Position>(); +``` + +Singleton components are created by adding the component to its own entity id. The above code examples are shortcuts for these regular API calls: + +```c +ecs_set(world, ecs_id(Position), Position, {10, 20}); + +const Position *p = ecs_get(world, ecs_id(Position), Position); +``` +```cpp +flecs::entity pos_e = world.id<Position>(); + +pos_e.set<Position>({10, 20}); + +const Position *p = pos_e.get<Position>(); +``` + +## Term +A term is the simplest kind of query in Flecs, and enables finding all entities for a single specific component, tag or pair. The following examples show how to find & iterate over all entities that have the `Position` component: + +```c +// Create an iterator that finds all entities that have Position +ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ ecs_id(Position) }); +while (ecs_term_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + // Iterate the entities & their Position components + for (int i = 0; i < it.count; i ++) { + printf("%s: {%f, %f}\n", ecs_get_name(world, it.entities[i]), + p[i].x, p[i].y); + } +} +``` +```cpp +// Iterate the entities & their Position components +world.each([](flecs::entity e, Position& p) { + std::cout << e.name() << ": {" << p.x << ", " << p.y << "}" << std::endl; +}); +``` + +## Filter +A filter is a list of terms that are matched against entities. Filters are cheap to create and match entities as iteration takes place. This makes them a good fit for scenarios where an application doesn't know in advance what it has to query for, a typical use case for this being runtime tags. Another advantage of filters is that while they can be reused, their cheap creation time doesn't require it. The following example shows a simple filter: + +```c +// Initialize a filter with 2 terms on the stack +ecs_filter_t f; +ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + { ecs_id(Position) }, + { ecs_pair(EcsChildOf, parent) } + } +}); + +// Iterate the filter results. Because entities are grouped by their type there +// are two loops: an outer loop for the type, and an inner loop for the entities +// for that type. +ecs_iter_t it = ecs_filter_iter(world, &f); +while (ecs_filter_next(&it)) { + // Each type has its own set of component arrays + Position *p = ecs_term(&it, Position, 1); + + // Iterate all entities for the type + for (int i = 0; i < it.count; i ++) { + printf("%s: {%f, %f}\n", ecs_get_name(world, it.entities[i]), + p[i].x, p[i].y); + } +} +``` +```cpp +// For simple queries the each function can be used +world.each([](Position& p, Velocity& v) { // flecs::entity argument is optional + p.x += v.x; + p.y += v.y; +}); + +// More complex filters can first be created, then iterated +auto f = world.filter_builder<Position>() + .term(flecs::ChildOf, parent) + .build(); + +// Option 1: each() function that iterates each entity +f.each([](flecs::entity e, Position& p) { + std::cout << e.name() << ": {" << p.x << ", " << p.y << "}" << std::endl; +}); + +// Option 2: iter() function that iterates each archetype +f.iter([](flecs::iter& it, Position *p) { + for (int i : it) { + std::cout << e.name() + << ": {" << p[i].x << ", " << p[i].y << "}" << std::endl; + } +}); +``` + +The time complexity of a filter is roughly O(n), where n is the number of archetypes matched by the term with the smallest number of matching archetypes. Each subsequent term adds a constant-time check per archetype, which makes the average time complexity O(n). + +Filters can use operators to exclude components, optionally match components or match one out of a list of components. Additionally filters may contain wildcards for terms which is especially useful when combined with pairs. + +The following example shows a filter that matches all entities with a parent that do not have `Position`: + +```c +ecs_filter_t f; +ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + { ecs_pair(EcsChildOf, EcsWildcard) } + { ecs_id(Position), .oper = EcsNot }, + } +}); + +// Iteration code is the same +``` +```cpp +auto f = world.filter_builder<>() + .term(flecs::ChildOf, flecs::Wildcard) + .term<Position>().oper(flecs::Not) + .build(); + +// Iteration code is the same +``` + +## Query +A query is like a filter in that it is a list of terms that is matched with entities. The difference with a filter is that queries cache their results, which makes them more expensive to create, but cheaper to iterate. Because a query caches a list of archetypes rather than a list of entities, the cache stabilizes fast as new archetypes are only created for new component combinations. This makes queries an attractive option for scenarios where an application knows in advance which things to query for (such as with systems), as over the lifespan of an application approximately no time is spent on finding the right entities. + +The API for queries looks very similar to filters: + +```c +// Create a query with 2 terms +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .terms = { + { ecs_id(Position) }, + { ecs_pair(EcsChildOf, EcsWildcard) } + } +}); + +ecs_iter_t it = ecs_query_iter(q); +while (ecs_query_next(&it)) { + // Same as for filters +} +``` +```cpp +// Create a query with two terms +auto q = world.query_builder<Position>() + .term(flecs::ChildOf, flecs::Wildcard) + .build(); + +// Iteration is the same as filters +``` + +Queries support additional features, such as breadth-first sorting based on relation (the `cascade` modifier) and sorting by component values. See the [query manual](Queries.md) for more details. + +## System +A system is a query combined with a callback. Systems can be either ran manually or ran as part of an ECS-managed main loop (see [Pipeline](#pipeline)). The system API looks similar to queries: + +```c +// Option 1, use the ECS_SYSTEM convenience macro +ECS_SYSTEM(world, Move, 0, Position, Velocity); +ecs_run(world, Move, delta_time, NULL); // Run system + +// Option 2, use the ecs_system_init function +ecs_entity_t move_sys = ecs_system_init(world, &(ecs_system_desc_t) { + .filter.terms = { + {ecs_id(Position)}, + {ecs_id(Velocity)}, + }, + .callback = Move +}); + +ecs_run(world, move_sys, delta_time, NULL); // Run system + +// The callback code (same for both options) +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x * it->delta_time; + p[i].y += v[i].y * it->delta_time; + } +} +``` +```cpp +// Use each() function that iterates each individual entity +auto move_sys = world.system<Position, Velocity>() + .iter([](flecs::iter it, Position *p, Velocity *v) { + p[i].x += v[i].x * it.delta_time(); + p[i].y += v[i].y * it.delta_time(); + }); + + // Just like with filters & queries, systems have both the iter() and + // each() methods to iterate entities. + +move_sys.run(); +``` + +Systems are stored as entities with an `EcsSystem` component (`flecs::System` in C++), similar to components. That means that an application can use a system as a regular entity: + +```c +printf("System: %s\n", ecs_get_name(world, move_sys)); +ecs_add(world, move_sys, EcsOnUpdate); +ecs_delete(world, move_sys); +``` +```cpp +std::cout << "System: " << move_sys.name() << std::endl; +move_sys.add(flecs::OnUpdate); +move_sys.destruct(); +``` + +## Pipeline +A pipeline is a list of tags that when matched, produces a list of systems to run. These tags are also referred to as a system "phase". Flecs comes with a default pipeline that has the following phases: + +```c +EcsOnLoad +EcsPostLoad +EcsPreUpdate +EcsOnUpdate +EcsOnValidate +EcsPostUpdate +EcsPreStore +EcsPostStore +``` +```cpp +flecs::OnLoad +flecs::PostLoad +flecs::PreUpdate +flecs::OnUpdate +flecs::PostUpdate +flecs::OnValidate +flecs::PostValidate +flecs::PreStore +flecs::PostStore +``` + +When a pipeline is executed, systems are ran in the order of the phases. This makes pipelines and phases the primary mechanism for defining ordering between systems. The following code shows how to assign systems to a pipeline, and how to run the pipeline with the `progress()` function: + +```c +ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); +ECS_SYSTEM(world, Transform, EcsPostUpdate, Position, Transform); +ECS_SYSTEM(world, Render, EcsOnStore, Transform, Mesh); + +ecs_progress(world, 0); // run systems in default pipeline +``` +```cpp +world.system<Position, Velocity>("Move").kind(flecs::OnUpdate).each( ... ); +world.system<Position, Transform>("Transform").kind(flecs::PostUpdate).each( ... ); +world.system<Transform, Mesh>("Render").kind(flecs::OnStore).each( ... ); + +world.progress(); +``` + +Because phases are just tags that are added to systems, applications can use the regular API to add/remove systems to a phase: +```c +ecs_remove_id(world, Move, EcsOnUpdate); +ecs_add_id(world, Move, EcsPostUpdate); +``` +```cpp +move_sys.add(flecs::OnUpdate); +move_sys.remove(flecs::PostUpdate); +``` + +Inside a phase systems are guaranteed to be ran in their declaration order. + +## Trigger +A trigger is a callback for an event for a single term. Triggers can be defined for `OnAdd`, `OnRemove`, `OnSet` and `UnSet` events. The API is similar to that of a system, but for a single term and an additional event. + +```c +ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .term = { ecs_id(Position) }, + .event = EcsOnSet, + .callback = OnSetPosition +}); + +// Callback code is same as system + +// Trigger the trigger +ecs_set(world, e, Position, {10, 20}); +``` +```cpp +// C++ triggers are created as a system with the phase set to the trigger event +world.system<Position>("OnSetPosition").kind(flecs::OnSet).each( ... ); + +// Trigger the trigger +e.set<Position>({10, 20}); +``` + +## Observer +An observer is like a trigger, but for multiple terms. Like triggers, observers can be defined for `OnAdd`, `OnRemove`, `OnSet` and `UnSet` events. The API is similar to that of a system. An observer is only triggered when all terms match the entity. + +```c +ecs_observer_init(world, &(ecs_observer_desc_t) { + .filter.terms = { { ecs_id(Position) }, { ecs_id(Velocity) }}, + .event = EcsOnSet, + .callback = OnSetPosition +}); + +// Callback code is same as system + +// Trigger the callback +ecs_entity_t e = ecs_new_id(world); // Doesn't trigger the observer +ecs_set(world, e, Position, {10, 20}); // Doesn't trigger the observer +ecs_set(world, e, Velocity, {1, 2}); // Triggers the observer +ecs_set(world, e, Position, {20, 40}); // Triggers the observer +``` +```cpp +world.observer<Position, Velocity>("OnSetPosition").event(flecs::OnSet).each( ... ); + +// Trigger the trigger +auto e = ecs.entity(); // Doesn't trigger the observer +e.set<Position>({10, 20}); // Doesn't trigger the observer +e.set<Velocity>({1, 2}); // Triggers the observer +e.set<Position>({20, 30}); // Triggers the observer +``` + +## Module +A module is a function that imports and organizes components, systems, triggers, observers, prefabs into the world as reusable units of code. A well designed module has no code that directly relies on code of another module, except for components definitions. All module contents are stored as child entities inside the module scope with the `ChildOf` relation. The following examples show how to define a module in C and C++: + +```c +// A bit of boiler plate for C modules +typedef struct MyModule { + int dummy; +} MyModule; + +#define MyModuleImportHandles(handles) + +void MyModuleImport(ecs_world_t *world) { + ECS_MODULE(world, MyModule); + + // Define components, systems, triggers, ... as usual. They will be + // automatically created inside the scope of the module. +} + +// Import code +ECS_IMPORT(world, MyModule); +``` +```cpp +struct my_module { + my_module(flecs::world& world) { + world.module<my_module>(); + + // Define components, systems, triggers, ... as usual. They will be + // automatically created inside the scope of the module. + } +}; + +// Import code +world.import<my_module>(); +``` + diff --git a/fggl/ecs2/flecs/docs/Relations.md b/fggl/ecs2/flecs/docs/Relations.md new file mode 100644 index 0000000000000000000000000000000000000000..9494654e981e0812a7e8fde93417f526c4072b2a --- /dev/null +++ b/fggl/ecs2/flecs/docs/Relations.md @@ -0,0 +1,840 @@ +# Relations +The relations feature allows for the creation of entity graphs, by specifying which relations entities have to each other. Relations are similar to regular components and tags, in that they can contain data or no data, and can be added and removed. The following code is a simple example that uses relations: + +```c +ecs_entity_t Likes = ecs_new_id(world); +ecs_entity_t Bob = ecs_new_id(world); +ecs_entity_t Alice = ecs_new_id(world); + +// Bob Likes Alice +ecs_add_pair(world, Bob, Likes, Alice); + +// Bob Likes Alice no more +ecs_remove_pair(world, Bob, Likes, Alice); +``` +```cpp +auto Likes = world.entity(); +auto Bob = world.entity(); +auto Alice = world.entity(); + +// Bob Likes Alice +Bob.add(Likes, Alice); + +// Bob Likes Alice no more +Bob.remove(Likes, Alice); +``` + +In this example, we would refer to `Bob` as the "subject", `Likes` as the "relation" and `Alice` as the "object". A relation when combined with an object is called a "relation pair". A pair combined with a subject is called a "relation triple". + +The same relation can be added multiple times to an entity, as long as its object is different: + +```c +ecs_entity_t Bob = ecs_new_id(world); +ecs_entity_t Eats = ecs_new_id(world); +ecs_entity_t Apples = ecs_new_id(world); +ecs_entity_t Pears = ecs_new_id(world); + +ecs_add_pair(world, Bob, Eats, Apples); +ecs_add_pair(world, Bob, Eats, Pears); + +ecs_has_pair(world, Bob, Eats, Apples); // true +ecs_has_pair(world, Bob, Eats, Pears); // true +``` +```cpp +auto Bob = world.entity(); +auto Eats = world.entity(); +auto Apples = world.entity(); +auto Pears = world.entity(); + +Bob.add(Eats, Apples); +Bob.add(Eats, Pears); + +Bob.has(Eats, Apples); // true +Bob.has(Eats, Pears); // true +``` + +An application can query for relations with the `(Relation, Object)` notation: + +```c +// Find all entities that eat apples +ecs_query_t *q = ecs_query_new(world, "(Eats, Apples)"); + +// Find all entities that eat anything +ecs_query_t *q = ecs_query_new(world, "(Eats, *)"); + +// Or with the ecs_query_init function: +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_pair(Eats, Apples)}} +}); +``` +```cpp +// Find all entities that eat apples +auto q = world.query("(Eats, Apples)"); + +// Find all entities that eat anything +auto q = world.query("(Eats, *)"); + +// With the query builder API: +auto q = world.query_builder<>() + .term(Eats, Apples) + .build(); + +// Or when using pair types, when both relation & object are compile time types: +auto q = world.query<flecs::pair<Eats, Apples>>(); +``` + +This example just shows a simple relation query. Relation queries are much more powerful than this as they provide the ability to match against entity graphs of arbitrary size. For more information on relation queries see the [Query manual](Queries.md). + +## Relation components +So far we've just seen relations with regular entities. When used in combination with components, relations can be associated with data: + +```c +typedef struct { + float amount; +} Requires; + +ECS_COMPONENT(world, Requires); +ECS_TAG(world, GigaWatts); + +ecs_entity_t delorean = ecs_new_id(world); + +ecs_set_pair(world, delorean, Requires, GigaWatts, {1.21}); + +const Requires *r = ecs_get_pair(world, delorean, Requires, GigaWatts); +printf("%f gigawatts!\n", r->amount); +``` +```cpp +struct Requires { + float amount; +}; + +struct Gigawatts { }; + +auto delorean = world.entity() + .set<Requires, Gigawatts>({1.21}); + +auto r = delorean.get<Requires, Gigawatts>(); +cout << r->value << " gigawatts!" << endl; +``` + +Relations composed out of types can use the `flecs::pair` template in the C++ API: + +```cpp +using RequiresGigawatts = flecs::pair<Requires, Gigawatts>; + +auto delorean = world.entity() + .set<RequiresGigawatts>({1.21}); + +auto r = delorean.get<RequiresGigaWatts>(); +cout << r->value << " gigawatts!" << endl; +``` + +An advantage of the `flecs::pair` template is that it can be used with function-style get/set operations: + +```cpp +// rvalue required as API converts value from 'Requires' to 'RequiresGigawatts' +e.set([](RequiresGigawatts&& r) { + r->value = 1.21; // Overloaded '->' operator to get the pair value +}) + +e.get([](const RequiresGigawatts& r) { + cout << r->value << " gigawatts!" << endl; +}); +``` + +In the above examples we added data to the "relation" part of the pair. If the relation part is a tag, we can also attach data to the object: + +```c +typedef struct { + const char *value; +} Event; + +ECS_TAG(world, Begin); +ECS_TAG(world, End); +ECS_COMPONENT(world, Event); + +ecs_entity_t universe = ecs_new_id(world); + +// Set data on the Event object, instead of the Begin, End relations: +ecs_set_pair_object(world, universe, Begin, Event, {"Big Bang"}); +ecs_set_pair_object(world, universe, End, Event, {"Heat Death"}); + +const Event *e = ecs_get_pair_object(world, universe, Begin, Event); +printf("In the beginning there was the %s\n", e->value); +``` +```cpp +struct Event { + const char *value; +}; + +struct Begin { }; +struct End { }; + +using BeginEvent = flecs::pair<Begin, Event>; +using EndEvent = flecs::pair<End, Event>; + +// Set data on the Event object, instead of the Begin, End relations: +auto universe = world.entity(); + .set<Begin, Event>({"Big Bang"}) + .set<End, Event>({"Heat Death"}); + +universe.get([](const BeginEvent& e) { + cout << "In the beginning there was the " << e.value << endl; +}); +``` + +### Using relations to add components multiple times +A limitation of components is that they can only be added once to an entity. Relations make it possible to get around this limitation, as a component _can_ be added multiple times, as long as the pair is unique. Pairs can be constructed on the fly from new entity identifiers, which means this is possible: + +```c +typedef struct { + float x; + float y; +} Position; + +ecs_entity_t e = ecs_new_id(world); + +ecs_entity_t first = ecs_new_id(world); +ecs_entity_t second = ecs_new_id(world); +ecs_entity_t third = ecs_new_id(world); + +// Add component position 3 times, for 3 different objects +ecs_add_pair(world, e, Position, first, {1, 2}); +ecs_add_pair(world, e, Position, second, {3, 4}); +ecs_add_pair(world, e, Position, third, {5, 6}); +``` +```cpp +struct Position { + float x; + float y; +} + +auto e = world.entity(); + +auto first = world.entity(); +auto second = world.entity(); +auto third = world.entity(); + +// Add component position 3 times, for 3 different objects +e.set<Position>(first, {1, 2}); +e.set<Position>(second, {3, 4}); +e.set<Position>(third, {5, 6}); +``` + +## Relation wildcards +When querying for relation pairs, it is often useful to be able to find all instances for a given relation or object. To accomplish this, an application can use wildcard expressions. Consider the following example, that queries for all entities with a `Likes` relation: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = { + {ecs_pair(Likes, EcsWildcard)} + } +}); + +ecs_iter_t it = ecs_query_iter(q); + +while (ecs_query_next(&it)) { + ecs_id_t id = ecs_term_id(&it, 1); // Obtain pair id + + // Get relation & object + ecs_entity_t rel = ecs_pair_relation(world, id); + ecs_entity_t obj = ecs_pair_object(world, id); + + for (int i = 0; i < it.count; it++) { + printf("entity %d has relation %s, %s\n", + it.entities[i], + ecs_get_name(world, rel), + ecs_get_name(world, obj)); + } +} +``` +```cpp +auto q = world.query_builder() + .term(Likes, flecs::Wildcard) + .build(); + +q.iter([]flecs::iter& it) { + auto id = it.term_id(1); + + for (auto i : it) { + cout << "entity " << it.entity(i) << " has relation " + << id.relation().name() << ", " + << id.object().name() << endl; + } +}); +``` + +Wildcards may appear in query expressions, using the `*` character: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "(Likes, *)" +}); +``` +```cpp +auto q = world.query("(Likes, *)"); +``` + +Wildcards may used for the relation or object part of a pair, or both: + +```cpp +"(Likes, *)" // Matches all Likes relations +"(*, Alice)" // Matches all relations with Alice as object +"(*, *)" // Matches all relations +``` + +## Inspecting relations +An application can use pair wildcard expressions to find all instances of a relation for an entity. The following example shows how to find all `Eats` relations for an entity: + +```c +// Bob eats apples and pears +ecs_entity_t Eats = ecs_new_id(world); +ecs_entity_t Apples = ecs_new_id(world); +ecs_entity_t Pears = ecs_new_id(world); + +ecs_entity_t Bob = ecs_new_id(world); +ecs_add_pair(world, Bob, Eats, Apples); +ecs_add_pair(world, Bob, Eats, Pears); + +// Find all (Eats, *) relations in Bob's type +ecs_type_t bob_type = ecs_get_type(world, Bob); +ecs_id_t wildcard = ecs_pair(Eats, EcsWildcard); +ecs_id_t *ids = ecs_vector_first(bob_type); +int32_t cur = -1; + +while (-1 != (cur = ecs_type_index_of(type, cur + 1, wildcard))) { + ecs_entity_t obj = ecs_pair_object(ids[cur]); + printf("Bob eats %s\n", ecs_get_name(world, obj)); +} +``` +```cpp +// Bob eats apples and pears +auto Bob = world.entity(); +auto Eats = world.entity(); +auto Apples = world.entity(); +auto Pears = world.entity(); + +Bob.add(Eats, Apples); +Bob.add(Eats, Pears); + +// Find all (Eats, *) relations in Bob's type +bob.match(world.pair(Eats, flecs::Wildcard), [](flecs::id id) { + cout << "Bob eats " << id.object().name() << endl; +}); + +// For object wildcard pairs, each() can be used: +bob.each(Eats, [](flecs::entity obj) { + cout << "Bob eats " << obj.name() << endl; +}) +``` + +## Builtin relations +Flecs comes with a few builtin relations that have special meaning within the framework. While they are implemented as regular relations and therefore obey the same rules as any custom relation, they are used to enhance the features of different parts of the framework. The following two sections describe the builtin relations of Flecs. + +### The IsA relation +The `IsA` relation is a builtin relation that allows applications to express that one entity is equivalent to another. This relation is at the core of component sharing and plays a large role in queries. The `IsA` relation can be used like any other relation, as is shown here: + +```c +ecs_entity_t Apple = ecs_new_id(world); +ecs_entity_t Fruit = ecs_new_id(world); +ecs_add_pair(world, Apple, EcsIsA, Fruit); +``` +```cpp +auto Apple = world.entity(); +auto Fruit = world.entity(); +Apple.add(flecs::IsA, Fruit); +``` + +In C++, adding an `IsA` relation has a shortcut: + +```cpp +Apple.is_a(Fruit); +``` + +This indicates to Flecs that an `Apple` is equivalent to a `Fruit` and should be treated as such. This equivalence is one-way, as a `Fruit` is not equivalent to an `Apple`. Another way to think about this is that `IsA` allows an application to express subsets and supersets. An `Apple` is a subset of `Fruit`. `Fruit` is a superset of `Apple`. + +We can also add `IsA` relations to `Apple`: + +```c +ecs_entity_t GrannySmith = ecs_new_id(world); +ecs_add_pair(world, GrannySmith, EcsIsA, Apple); +``` +```cpp +auto GrannySmith = world.entity(); +GrannySmith.add(flecs::IsA, Apple); +``` + +This specifies that `GrannySmith` is a subset of `Apple`. A key thing to note here is that because `Apple` is a subset of `Fruit`, `GrannySmith` is a subset of `Fruit` as well. This means that if an application were to query for `(IsA, Fruit)` it would both match `Apple` and `GrannySmith`. This property of the `IsA` relationhip is called "transitivity" and it is a feature that can be applied to any relation. See the [section on Transitivity](#transitive-relations) for more details. + +#### Component sharing +An entity with an `IsA` relation to another entity is equivalent to the other entity. So far the examples showed how querying for an `IsA` relation will find the subsets of the thing that was queried for. In order for entities to be treated as true equivalents though, everything the supserset contains (its components, tags, relations) must also be found on the subsets. Consider: + +```c +ecs_entity_t Spaceship = ecs_new_id(world); +ecs_set(world, Spaceship, MaxSpeed, {100}); +ecs_set(world, SpaceShip, Defense, {50}); + +ecs_entity_t Frigate = ecs_new_id(world); +ecs_add(world, Frigate, EcsIsA, Spaceship); +ecs_set(world, Frigate, Defense, {100}); +``` +```cpp +auto Spaceship = world.entity() + .set<MaxSpeed>({100}) + .set<Defense>({50}); + +auto Frigate = world.entity() + .is_a(SpaceShip) // shorthand for .add(flecs::IsA, Spaceship) + .set<Defense>({75}); +``` + +Here, the `Frigate` "inherits" the contents of `SpaceShip`. Even though `MaxSpeed` was never added directly to `Frigate`, an application can do this: + +```c +// Obtain the inherited component from Spaceship +const MaxSpeed *v = ecs_get(world, Frigate, MaxSpeed); +v->value == 100; // true +``` +```cpp +// Obtain the inherited component from Spaceship +const MaxSpeed *v = Frigate.get<MaxSpeed>(); +v->value == 100; // true +``` + +While the `Frigate` entity also inherited the `Defense` component, it overrode this with its own value, so that the following example works: + +```c +// Obtain the overridden component from Frigate +const Defense *v = ecs_get(world, Frigate, Defense); +v->value == 75; // true +``` +```cpp +// Obtain the overridden component from Frigate +const Defense *v = Frigate.get<Defense>(); +v->value == 75; // true +``` + +The ability to share components is also applied transitively, so `Frigate` could be specialized further into a `FastFrigate`: + +```c +ecs_entity_t FastFrigate = ecs_new_id(world); +ecs_add(world, FastFrigate, EcsIsA, Frigate); +ecs_set(world, FastFrigate, MaxSpeed, {200}); + +// Obtain the overridden component from FastFrigate +const MaxSpeed *s = ecs_get(world, Frigate, MaxSpeed); +s->value == 200; // true + +// Obtain the inherited component from Frigate +const Defense *d = Frigate.get<Defense>(); +d->value == 75; // true +``` +```cpp +auto FastFrigate = world.entity() + .is_a(Frigate) + .set<MaxSpeed>({200}); + +// Obtain the overridden component from FastFrigate +const MaxSpeed *s = Frigate.get<MaxSpeed>(); +s->value == 200; // true + +// Obtain the inherited component from Frigate +const Defense *d = Frigate.get<Defense>(); +d->value == 75; // true +``` + +This ability to inherit and override components is one of the key enabling features of Flecs prefabs, and is further explained in the [Inheritance section](Manual.md#Inheritance) of the manual. + +#### Final entities +Entities can be annotated with the `Final` property, which prevents using them with the `IsA` relation. This is similar to the concept of a final class as something that cannot be extended. The following example shows how to add final to an entity: + +```c +ecs_entity_t e = ecs_new_id(world); +ecs_add_id(world, e, EcsFinal); + +ecs_entity_t i = ecs_new_id(world); +ecs_add_pair(world, e, i, EcsIsA, e); // not allowed +``` +```cpp +auto e = ecs.entity() + .add(flecs::Final); + +auto i = ecs.entity() + .is_a(e); // not allowed +``` + +Queries may use the final property to optimize, as they do not have to explore subsets of a final entity. For more information on how queries interpret final, see the [Query manual](Queries.md). By default, all components are created final. + +### The ChildOf relation +The `ChildOf` relation is the builtin relation that allows for the creation of entity hierarchies. The following example shows how hierarchies can be created with `ChildOf`: + +```c +ecs_entity_t Spaceship = ecs_new_id(world); +ecs_entity_t Cockpit = ecs_new_id(world); + +ecs_add_pair(world, Cockpit, EcsChildOf, Spaceship); +``` +```cpp +auto Spaceship = world.entity(); +auto Cockpit = world.entity(); + +Cockpit.add(flecs::ChildOf, Spaceship); +``` + +In C++, adding a `ChildOf` relation has a shortcut: + +```cpp +Cockpit.child_of(Spaceship); +``` + +The `ChildOf` relation is defined so that when a parent is deleted, its children are also deleted. For more information on specifying cleanup behavior for relations, see the [Relation cleanup properties](#relation-cleanup-properties) section. + +The `ChildOf` relation is defined as a regular relation in Flecs. There are however a number of features that interact with `ChildOf`. The following sections describe these features. + +#### Namespacing +Entities in flecs can have names, and name lookups can be relative to a parent. Relative name lookups can be used as a namespacing mechanism to prevent clashes between entity names. This example shows a few examples of name lookups in combination with hierarchies: + +```c +// Create two entities with a parent/child name +ecs_entity_t parent = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "Parent" +}); + +ecs_entity_t child = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "Child" +}); + +// Create the hierarchy +ecs_add_pair(world, child, EcsChildOf, parent); + +child = ecs_lookup_fullpath(world, "Parent::Child"); // true +child = ecs_lookup_path(world, parent, "Child"); // true +``` +```cpp +auto parent = world.entity("Parent"); +auto child = world.entity("Child") + .child_of(parent); + +child == world.lookup("Parent::Child"); // true +child == parent.lookup("Child"); // true +``` + +#### Scoping +In some scenarios a number of entities all need to be created with the same parent. Rather than adding the relation to each entity, it is possible to configure the parent as a scope, which ensures that all entities created afterwards are created in the scope. The following example shows how: + +```c +ecs_entity_t parent = ecs_new_id(world); +ecs_entity_t prev = ecs_set_scope(world, parent); + +// Note that we're not using the ecs_new_id function for the children. This +// function only generates a new id, and does not add the scope to the entity. +ecs_entity_t child_a = ecs_new(world, 0); +ecs_entity_t child_b = ecs_new(world, 0); + +// Restore the previous scope +ecs_set_scope(world, prev); + +ecs_has_pair(world, child_a, EcsChildOf, parent); // true +ecs_has_pair(world, child_b, EcsChildOf, parent); // true +``` +```cpp +auto parent = world.entity(); +auto prev = world.set_scope(parent); + +auto child_a = world.entity(); +auto child_b = world.entity(); + +// Restore the previous scope +world.set_scope(prev); + +child_a.has(flecs::ChildOf, parent); // true +child_b.has(flecs::ChildOf, parent); // true +``` + +Scopes in C++ can also be used with the `scope` function on an entity, which accepts a (typcially lambda) function: + +```cpp +auto parent = world.entity().scope([&]{ + auto child_a = world.entity(); + auto child_b = world.entity(); + + child_a.has(flecs::ChildOf, parent); // true + child_b.has(flecs::ChildOf, parent); // true +}); +``` + +Scopes are the mechanism that ensure contents of a module are created as children of the module, without having to explicitly add the module as a parent. + +## Relation properties +Relations can be assigned properties that change the way they are treated by flecs. Properties are modelled as components, tags and relations that you can add to the relation entity. + +### Relation cleanup properties +When either the relation or object entity of a relation is deleted, by default all of the instances of the relation are removed from the store. Consider the following example: + +```c +ecs_entity_t Likes = ecs_new_id(world); +ecs_entity_t Bob = ecs_new_id(world); +ecs_entity_t Alice = ecs_new_id(world); + +ecs_add_pair(world, Bob, Likes, Alice); + +// This removes (Likes, Alice) from Bob, and all other entities that had a +// relation with Alice +ecs_delete(world, Alice); +``` +```cpp +auto Likes = world.entity(); +auto Bob = world.entity(); +auto Alice = world.entity(); + +Bob.add(Likes, Alice); + +// This removes (Likes, Alice) from Bob, and all other entities that had a +// relation with Alice +Alice.destruct(); +``` + +This behavior can be customized with cleanup properties as the above behavior is not always what you want. A typical example is the builtin `ChildOf` relation, where child entities should be deleted when the parent is deleted: + +```c +ecs_entity_t Spaceship = ecs_new_id(world); +ecs_entity_t Cockpit = ecs_new_id(world); + +ecs_add_pair(world, Cockpit, EcsChildOf, Spaceship); + +// This deletes both the spaceship and the cockpit entity +ecs_delete(world, Spaceship); +``` +```cpp +auto Spaceship = world.entity(); +auto Cockpit = world.entity(); + +Cockpit.child_of(Spaceship); + +// This deletes both the spaceship and the cockpit entity +Spaceship.destruct(); +``` + +To customize this behavior, an application can add the `OnDeleteObject` policy to the relation. The following examples show how: + +```c +ecs_entity_t Likes = ecs_new_id(world); +ecs_entity_t Bob = ecs_new_id(world); +ecs_entity_t Alice = ecs_new_id(world); + +ecs_add_pair(world, Bob, Likes, Alice); + +// When Alice is deleted, remove (Likes, Alice) from Bob +ecs_add_pair(world, Likes, EcsOnDeleteObject, EcsRemove); + +// When Alice is deleted, delete Bob +ecs_add_pair(world, Likes, EcsOnDeleteObject, EcsDelete); + +// When Alice is deleted, throw an error (assert) +ecs_add_pair(world, Likes, EcsOnDeleteObject, EcsThrow); +``` +```cpp +auto Likes = world.entity(); +auto Bob = world.entity(); +auto Alice = world.entity(); + +Bob.add(Likes, Alice); + +// When Alice is deleted, remove (Likes, Alice) from Bob +Likes.add(flecs::OnDeleteObject, flecs::Remove) + +// When Alice is deleted, delete Bob +Likes.add(flecs::OnDeleteObject, flecs::Delete); + +// When Alice is deleted, throw an error (assert) +Likes.add(flecs::OnDeleteObject, flecs::Throw); +``` + +An application may also specify what cleanup action should be performed if the relation itself is deleted with the `OnDelete` policy: + +```c +ecs_entity_t Likes = ecs_new_id(world); +ecs_entity_t Bob = ecs_new_id(world); +ecs_entity_t Alice = ecs_new_id(world); + +ecs_add_pair(world, Bob, Likes, Alice); + +// When Likes is deleted, remove (Likes, Alice) from Bob +ecs_add_pair(world, Likes, EcsOnDelete, EcsRemove); + +// When Likes is deleted, delete Bob +ecs_add_pair(world, Likes, EcsOnDelete, EcsDelete); + +// When Likes is deleted, throw an error (assert) +ecs_add_pair(world, Likes, EcsOnDelete, EcsThrow); +``` +```cpp +auto Likes = world.entity(); +auto Bob = world.entity(); +auto Alice = world.entity(); + +Bob.add(Likes, Alice); + +// When Likes is deleted, remove (Likes, Alice) from Bob +Likes.add(flecs::OnDelete, flecs::Remove) + +// When Likes is deleted, delete Bob +Likes.add(flecs::OnDelete, flecs::Delete); + +// When Likes is deleted, throw an error (assert) +Likes.add(flecs::OnDelete, flecs::Throw); +``` + +By default, components are created with the `(OnDelete, Throw)` policy. + +### Transitive relations +Relations can be marked as transitive. A formal-ish definition if transitivity in the context of relations is: + +``` +If Relation(EntityA, EntityB) And Relation(EntityB, EntityC) Then Relation(EntityA, EntityC) +``` + +What this means becomes more obvious when translated to a real-life example: + +``` +If Manhattan is located in New York, and New York is located in the USA, then Manhattan is located in the USA. +``` + +In this example, `LocatedIn` is the relation and `Manhattan`, `New York` and `USA` are entities `A`, `B` and `C`. Another common example of transitivity is found in OOP inheritance: + +``` +If a Square is a Rectangle and a Rectangle is a Shape, then a Square is a Shape. +``` + +In this example `IsA` is the relation and `Square`, `Rectangle` and `Shape` are the entities. + +When relations in Flecs are marked as transitive, queries can follow the transitive relation to see if an entity matches. Consider this example dataset: + +```c +ecs_entity_t LocatedIn = ecs_new_id(world); +ecs_entity_t Manhattan = ecs_new_id(world); +ecs_entity_t NewYork = ecs_new_id(world); +ecs_entity_t USA = ecs_new_id(world); + +ecs_add_pair(world, Manhattan, LocatedIn, NewYork); +ecs_add_pair(world, NewYork, LocatedIn, USA); +``` +```cpp +auto LocatedIn = world.entity(); +auto Manhattan = world.entity(); +auto NewYork = world.entity(); +auto USA = world.entity(); + +ManHattan.add(LocatedIn, NewYork); +NewYork.add(LocatedIn, USA); +``` + +If we were now to query for `(LocatedIn, USA)` we would only match `NewYork`, because we never added `(LocatedIn, USA)` to `Manhattan`. To make sure queries `Manhattan` as well we have to make the `LocatedIn` relation transitive. We can simply do this by adding the transitive property to the relation entity: + +```c +ecs_add_id(world, LocatedIn, Transitive); +``` +```cpp +LocatedIn.add(flecs::Transitive); +``` + +When now querying for `(LocatedIn, USA)`, the query will follow the `LocatedIn` relation and return both `NewYork` and `Manhattan`. For more details on how queries use transitivity, see the section in the query manual on transitivity: [Query transitivity](Queries.md#Transitivity). + +### Tag relations +A relation can be marked as a tag in which case it will never contain data. By default the data associated with a pair is determined by whether either the relation or object are components. For some relations however, even if the object is a component, no data should be added to the relation. Consider the following example: + +```c +typedef struct { + float x; + float y; +} Position; + +ECS_TAG(world, Serializable); +ECS_COMPONENT(world, Position); + +ecs_entity_t e = ecs_new_id(world); +ecs_set(world, e, Position, {10, 20}); +ecs_add_pair(world, e, Serializable, ecs_id(Position)); + +// Gets value from Position component +const Position *p = ecs_get(world, e, Position); + +// Gets (unintended) value from (Serializable, Position) pair +const Position *p = ecs_get_pair_object(world, e, Serializable, Position); +``` +```cpp +struct Serializable { }; // Tag, contains no data + +struct Position { + float x, y; +}; + +auto e = ecs.entity() + .set<Position>({10, 20}) + .add<Serializable, Position>(); // Because Serializable is a tag, the pair + // has a value of type Position + +// Gets value from Position component +const Position *p = e.get<Position>(); + +// Gets (unintended) value from (Serializable, Position) pair +const Position *p = e.get<Serializable, Position>(); +``` + +To prevent data from being associated with pairs that can apply to components, the `Tag` property can be added to relations: + +```c +// Ensure that Serializable never contains data +ecs_add_id(world, Serializable, EcsTag); + +// Because Serializable is marked as a Tag, no data is added for the pair +// even though Position is a component +ecs_add_pair(world, e, Serializable, ecs_id(Position)); + +// This is still OK +const Position *p = ecs_get(world, e, Position); + +// This no longer works, the pair has no data +const Position *p = ecs_get_pair_object(world, e, Serializable, Position); +``` +```cpp +// Ensure that Serializable never contains data +ecs.component<Serializable>() + .add<flecs::Tag>(); + +auto e = ecs.entity() + .set<Position>({10, 20}) + .add<Serializable, Position>(); // Because Serializable marked as a Tag, no + // data is added for the pair even though + // Position is a component + +// Gets value from Position component +const Position *p = e.get<Position>(); + +// This no longer works, the pair has no data +const Position *p = e.get<Serializable, Position>(); +``` + +The `Tag` property is only interpreted when it is added to the relation part of a pair. + +## Relation performance +A relation that does not have any data has the same performance as a regular tag. A relation that does have data has the same performance as a component. + +To understand how this works consider that on the storage level a component or tag is represented by a unique id and a size. The id identifies the component, whereas the size is the size of the component data. If an id represents a tag, the size is 0. An entity can only have a single instance of each unique id, which means that you can only add component `Position` once to the same entity. + +On the storage level, relations are nothing more than unique ids, which are generated from a (relation, object) pair by the API. Each unique (relation, object) combination produces a unique id that can then be added or removed from an entity in the storage. For the most part the storage is unaware of whether an id represents a relation, component or tag which means their performance is equivalent. + +The following code example shows how the API generates a unique id from a pair before passing it down to the storage: + +```c +// This function call +const Requires *r = ecs_get_pair(world, e, Requires, Gigawatts); + +// Is equivalent to this code +ecs_id_t pair_id = ecs_pair(ecs_id(Requires), Gigawatts); +const Required *r = ecs_get_id(world, e, pair_id); // Regular get + +// The pair_id is generated by storing the relation id in the upper 32 bits of +// the identifier and the object in the lower 32 bits: +ecs_id_t pair_id = Requires_id << 32 | Gigawatts; +``` diff --git a/fggl/ecs2/flecs/docs/flecs-add-component-flow.png b/fggl/ecs2/flecs/docs/flecs-add-component-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..3115ef13789995499a27038a1cf1aab0b6dcda98 Binary files /dev/null and b/fggl/ecs2/flecs/docs/flecs-add-component-flow.png differ diff --git a/fggl/ecs2/flecs/docs/flecs-architecture-overview.png b/fggl/ecs2/flecs/docs/flecs-architecture-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..4431de7a04fb7585a5687b1cd0c350aa4a497f03 Binary files /dev/null and b/fggl/ecs2/flecs/docs/flecs-architecture-overview.png differ diff --git a/fggl/ecs2/flecs/docs/flecs-quickstart-overview.png b/fggl/ecs2/flecs/docs/flecs-quickstart-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..38dee804019f74f6822b044ed0495cbe5b007f79 Binary files /dev/null and b/fggl/ecs2/flecs/docs/flecs-quickstart-overview.png differ diff --git a/fggl/ecs2/flecs/docs/flecs-remove-component-flow.png b/fggl/ecs2/flecs/docs/flecs-remove-component-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..a59a322ab9c536dbd59e6c297a466cc39a126831 Binary files /dev/null and b/fggl/ecs2/flecs/docs/flecs-remove-component-flow.png differ diff --git a/fggl/ecs2/flecs/docs/flecs-staging-flow.png b/fggl/ecs2/flecs/docs/flecs-staging-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5e9dc1234409ba86d6c38933ceaad69bb365cb Binary files /dev/null and b/fggl/ecs2/flecs/docs/flecs-staging-flow.png differ diff --git a/fggl/ecs2/flecs/examples/BUILD.bazel b/fggl/ecs2/flecs/examples/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..0327fb4488b5300eca8feb2bf9a6fdb969222870 --- /dev/null +++ b/fggl/ecs2/flecs/examples/BUILD.bazel @@ -0,0 +1,9 @@ +cc_library( + name = "os-api", + visibility = ["//:__subpackages__"], + deps = ["//:flecs", "@bake//:util"], + + srcs = glob(["os_api/flecs-os_api-bake/src/**/*.c", "os_api/flecs-os_api-bake/src/**/*.h"]), + hdrs = glob(["os_api/flecs-os_api-bake/include/**/*.h"]), + includes = ["os_api/flecs-os_api-bake/include"], +) diff --git a/fggl/ecs2/flecs/examples/CMakeLists.txt b/fggl/ecs2/flecs/examples/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ca53adafd760032cbbcb426430c53a83f091a7af --- /dev/null +++ b/fggl/ecs2/flecs/examples/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.5) + +project(flecs_example LANGUAGES C CXX) + +add_subdirectory(.. ${CMAKE_CURRENT_BINARY_DIR}/flecs) + +include(../cmake/target_default_compile_options.cmake) +include(../cmake/target_default_compile_warnings.cmake) + +macro(SUB_DIR_LIST RESULT CURRENT_DIR) + + file(GLOB CHILDREN LIST_DIRECTORIES true RELATIVE ${CURRENT_DIR} ${CURRENT_DIR}/*) + set(DIRLIST "") + + foreach (CHILD ${CHILDREN}) + if (IS_DIRECTORY ${CURRENT_DIR}/${CHILD}) + list(APPEND DIRLIST ${CHILD}) + endif () + endforeach () + + set(${RESULT} ${DIRLIST}) + +endmacro() + +get_filename_component(PARENT_DIR ../ ABSOLUTE) +macro(CREATE_TARGET NAME PARENT) + + set(TARGET_NAME example_${PARENT}_${NAME}) + set(TARGET_NAME_STATIC example_${PARENT}_${NAME}_static) + set(TARGET_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${PARENT}/${NAME}) + set(SRC_FILES "") + aux_source_directory(${TARGET_PATH}/src/ SRC_FILES) + + add_executable(${TARGET_NAME} ${SRC_FILES}) + add_executable(${TARGET_NAME_STATIC} ${SRC_FILES}) + + if (${PARENT} STREQUAL c) + target_default_compile_options_c(${TARGET_NAME}) + target_default_compile_warnings_c(${TARGET_NAME_STATIC}) + else () + target_default_compile_options_cxx(${TARGET_NAME}) + target_default_compile_warnings_cxx(${TARGET_NAME_STATIC}) + endif () + + target_include_directories(${TARGET_NAME} PUBLIC ${PARENT_DIR}/include) + target_include_directories(${TARGET_NAME_STATIC} PUBLIC ${PARENT_DIR}/include) + target_include_directories(${TARGET_NAME} PUBLIC ${TARGET_PATH}/include) + target_include_directories(${TARGET_NAME_STATIC} PUBLIC ${TARGET_PATH}/include) + target_link_libraries(${TARGET_NAME} flecs) + target_link_libraries(${TARGET_NAME_STATIC} flecs_static) + +endmacro() + +SUB_DIR_LIST(C_SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}/c) +foreach (C_SUBDIR ${C_SUBDIRS}) + CREATE_TARGET(${C_SUBDIR} c) +endforeach () + +SUB_DIR_LIST(CPP_SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}/cpp) +foreach (CPP_SUBDIR ${CPP_SUBDIRS}) + CREATE_TARGET(${CPP_SUBDIR} cpp) +endforeach () diff --git a/fggl/ecs2/flecs/examples/c/01_helloworld/include/helloworld.h b/fggl/ecs2/flecs/examples/c/01_helloworld/include/helloworld.h new file mode 100644 index 0000000000000000000000000000000000000000..aa7484570800beb9dfa91071e7be9c78526c0de7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/01_helloworld/include/helloworld.h @@ -0,0 +1,16 @@ +#ifndef HELLOWORLD_H +#define HELLOWORLD_H + +/* This generated file contains includes for project dependencies */ +#include "helloworld/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/01_helloworld/include/helloworld/bake_config.h b/fggl/ecs2/flecs/examples/c/01_helloworld/include/helloworld/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d59119a3974e2d11106e8f5203d92118974ae194 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/01_helloworld/include/helloworld/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 HELLOWORLD_BAKE_CONFIG_H +#define HELLOWORLD_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/01_helloworld/project.json b/fggl/ecs2/flecs/examples/c/01_helloworld/project.json new file mode 100644 index 0000000000000000000000000000000000000000..61df04de1a4bd589816cf75796c0e890fdace64e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/01_helloworld/project.json @@ -0,0 +1,12 @@ +{ + "id": "helloworld", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/01_helloworld/src/main.c b/fggl/ecs2/flecs/examples/c/01_helloworld/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..eb32f5096f64428d912604b9a39426cb680589d1 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/01_helloworld/src/main.c @@ -0,0 +1,29 @@ +#include <helloworld.h> + +typedef struct { + double x, y; +} Position; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register a component with the world. */ + ECS_COMPONENT(world, Position); + + /* Create a new empty entity */ + ECS_ENTITY(world, MyEntity, 0); + + /* Set the Position component on the entity */ + ecs_set(world, MyEntity, Position, {10, 20}); + + /* Get the Position component */ + const Position *p = ecs_get(world, MyEntity, Position); + + printf("Position of %s is {%f, %f}\n", + ecs_get_name(world, MyEntity), p->x, p->y); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/02_simple_system/include/simple_system.h b/fggl/ecs2/flecs/examples/c/02_simple_system/include/simple_system.h new file mode 100644 index 0000000000000000000000000000000000000000..ec6497bfe58e8674a7fa232b4933a0111550b754 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/02_simple_system/include/simple_system.h @@ -0,0 +1,16 @@ +#ifndef SIMPLE_SYSTEM_H +#define SIMPLE_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "simple_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/02_simple_system/include/simple_system/bake_config.h b/fggl/ecs2/flecs/examples/c/02_simple_system/include/simple_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..044ec0f59d8f0d9f8df62a3748632f6ac2f36a34 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/02_simple_system/include/simple_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_SYSTEM_BAKE_CONFIG_H +#define SIMPLE_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/02_simple_system/project.json b/fggl/ecs2/flecs/examples/c/02_simple_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..87c6a4855998054b20586cdc07b904d0b475e6a3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/02_simple_system/project.json @@ -0,0 +1,12 @@ +{ + "id": "simple_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/02_simple_system/src/main.c b/fggl/ecs2/flecs/examples/c/02_simple_system/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f97c54249eca867afdd23982c49cdfe7b5701f3a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/02_simple_system/src/main.c @@ -0,0 +1,46 @@ +#include <simple_system.h> + +/* Component type */ +typedef struct Message { + const char *text; +} Message; + +/* Must have the same name as the ECS_SYSTEM definition */ +void PrintMessage(ecs_iter_t *it) { + /* Get a pointer to the array of the first column in the system. The order + * of columns is the same as the one provided in the system signature. */ + Message *msg = ecs_term(it, Message, 1); + + /* Iterate all the messages */ + for (int i = 0; i < it->count; i ++) { + printf("%s\n", msg[i].text); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Define component */ + ECS_COMPONENT(world, Message); + + /* Define a system called PrintMessage that is executed every frame, and + * subscribes for the 'Message' component */ + ECS_SYSTEM(world, PrintMessage, EcsOnUpdate, Message); + + /* Create new entity, add the component to the entity */ + ecs_entity_t e = ecs_new(world, Message); + ecs_set(world, e, Message, {.text = "Hello Flecs!"}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application simple_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/03_move_system/include/move_system.h b/fggl/ecs2/flecs/examples/c/03_move_system/include/move_system.h new file mode 100644 index 0000000000000000000000000000000000000000..ca50325b19f3797633ddbc17aa311e752db2a705 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/03_move_system/include/move_system.h @@ -0,0 +1,16 @@ +#ifndef MOVE_SYSTEM_H +#define MOVE_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "move_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/03_move_system/include/move_system/bake_config.h b/fggl/ecs2/flecs/examples/c/03_move_system/include/move_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..dcac16e9bbc3f581144dc57d11a5eea3a42d41c0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/03_move_system/include/move_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 MOVE_SYSTEM_BAKE_CONFIG_H +#define MOVE_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/03_move_system/project.json b/fggl/ecs2/flecs/examples/c/03_move_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f8d4aa667b9c237dcfee22e2d035ec9f727e4d3e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/03_move_system/project.json @@ -0,0 +1,12 @@ +{ + "id": "move_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/03_move_system/src/main.c b/fggl/ecs2/flecs/examples/c/03_move_system/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..5063538f70f8eb256588fd5fd7c7ae25148a8bf2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/03_move_system/src/main.c @@ -0,0 +1,59 @@ +#include <move_system.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Define a system called Move that is executed every frame, and subscribes + * for the 'Position' and 'Velocity' components */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + /* Create an entity with Position and Velocity. Creating an entity with the + * ECS_ENTITY macro is an easy way to create entities with multiple + * components. Additionally, entities created with this macro can be looked + * up by their name ('MyEntity'). */ + ECS_ENTITY(world, MyEntity, Position, Velocity); + + /* Initialize values for the entity */ + ecs_set(world, MyEntity, Position, {0, 0}); + ecs_set(world, MyEntity, Velocity, {1, 1}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/04_simple_module/include/simple_module.h b/fggl/ecs2/flecs/examples/c/04_simple_module/include/simple_module.h new file mode 100644 index 0000000000000000000000000000000000000000..e6ee7b7a35b79e35fdd360d6999266c38f9bff4f --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/04_simple_module/include/simple_module.h @@ -0,0 +1,44 @@ +#ifndef SIMPLE_MODULE_H +#define SIMPLE_MODULE_H + +/* This generated file contains includes for project dependencies */ +#include "simple_module/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +/* This type is used to store handles to everything that the module contains. + * When the module is loaded, this type will be registered as component, and + * added to the singleton entity so applications can access module handles from + * anywhere. */ +typedef struct SimpleModule { + ECS_DECLARE_COMPONENT(Position); + ECS_DECLARE_COMPONENT(Velocity); + ECS_DECLARE_ENTITY(Move); +} SimpleModule; + +/* This is the function that implements the module loader. It is automatically + * invoked by the ECS_IMPORT macro. */ +void SimpleModuleImport( + ecs_world_t *world); + +/* This macro is used to declare variables that contain the handles inside the + * module. It is invoked by the ECS_IMPORT macro to declare variables in the + * scope where the macro is invoked so the contents of the module can be used + * after the ECS_IMPORT macro. */ +#define SimpleModuleImportHandles(handles)\ + ECS_IMPORT_COMPONENT(handles, Position);\ + ECS_IMPORT_COMPONENT(handles, Velocity);\ + ECS_IMPORT_ENTITY(handles, Move); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/examples/c/04_simple_module/include/simple_module/bake_config.h b/fggl/ecs2/flecs/examples/c/04_simple_module/include/simple_module/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..bf3bba2128467f18b3d77af9c21c6957b73f0719 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/04_simple_module/include/simple_module/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_MODULE_BAKE_CONFIG_H +#define SIMPLE_MODULE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/04_simple_module/project.json b/fggl/ecs2/flecs/examples/c/04_simple_module/project.json new file mode 100644 index 0000000000000000000000000000000000000000..ba05bc3cfdf124162fb73c4efc30eadf5a79f84a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/04_simple_module/project.json @@ -0,0 +1,12 @@ +{ + "id": "simple_module", + "type": "application", + "value": { + "author": "Sander Mertens", + "description": "Flecs module package", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/04_simple_module/src/main.c b/fggl/ecs2/flecs/examples/c/04_simple_module/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f18aec9b10780cc33f5fdbdda6bd99d6f5140c9c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/04_simple_module/src/main.c @@ -0,0 +1,30 @@ +#include <simple_module.h> + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Import SimpleModule module. After this statement, the application is able + * to use the Position and Velocity components, and the Move system will be + * loaded and matched against entities with Position, Velocity. */ + ECS_IMPORT(world, SimpleModule); + + /* Create an entity with Position and Velocity */ + ECS_ENTITY(world, MyEntity, simple.module.Position, simple.module.Velocity); + + /* Initialize values for the entity */ + ecs_set(world, MyEntity, Position, {0, 0}); + ecs_set(world, MyEntity, Velocity, {1, 1}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems, this will run the Move system for MyEntity */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/04_simple_module/src/simple_module.c b/fggl/ecs2/flecs/examples/c/04_simple_module/src/simple_module.c new file mode 100644 index 0000000000000000000000000000000000000000..62e71e31d53ef393843c1c702c80e3bf64627107 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/04_simple_module/src/simple_module.c @@ -0,0 +1,36 @@ +#include <simple_module.h> + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +void SimpleModuleImport( + ecs_world_t *world) +{ + /* Define module */ + ECS_MODULE(world, SimpleModule); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Define a system called Move that is executed every frame, and subscribes + * for the 'Position' and 'Velocity' components */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + /* Export handles to module contents */ + ECS_SET_COMPONENT(Position); + ECS_SET_COMPONENT(Velocity); + ECS_SET_ENTITY(Move); +} diff --git a/fggl/ecs2/flecs/examples/c/05_simple_triggers/include/simple_triggers.h b/fggl/ecs2/flecs/examples/c/05_simple_triggers/include/simple_triggers.h new file mode 100644 index 0000000000000000000000000000000000000000..62854250473d4c6b0ce0b58ced220eafc7192b93 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/05_simple_triggers/include/simple_triggers.h @@ -0,0 +1,16 @@ +#ifndef SIMPLE_TRIGGERS_H +#define SIMPLE_TRIGGERS_H + +/* This generated file contains includes for project dependencies */ +#include "simple_triggers/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/05_simple_triggers/include/simple_triggers/bake_config.h b/fggl/ecs2/flecs/examples/c/05_simple_triggers/include/simple_triggers/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..242201cfe9d98d77b4d6909f1968c4c2c4a63d93 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/05_simple_triggers/include/simple_triggers/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_TRIGGERS_BAKE_CONFIG_H +#define SIMPLE_TRIGGERS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/05_simple_triggers/project.json b/fggl/ecs2/flecs/examples/c/05_simple_triggers/project.json new file mode 100644 index 0000000000000000000000000000000000000000..c04c813fa9be713804ec3c0ee09ae52856315b3c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/05_simple_triggers/project.json @@ -0,0 +1,12 @@ +{ + "id": "simple_triggers", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/05_simple_triggers/src/main.c b/fggl/ecs2/flecs/examples/c/05_simple_triggers/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..22ad2a3c120d8274cac602460ee954d5d236bcf9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/05_simple_triggers/src/main.c @@ -0,0 +1,56 @@ +#include <simple_triggers.h> + +typedef struct { + double x, y; +} Position; + +/* This system will be called when Position is added */ +void AddPosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + printf("Position added\n"); + } +} + +/* This system will be called when Position is removed */ +void RemovePosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + printf("Position removed -> {%f, %f}\n", + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register Position component */ + ECS_COMPONENT(world, Position); + + /* Register two systems that are executed when Position is added or removed + * to entities. */ + ECS_TRIGGER(world, AddPosition, EcsOnAdd, Position); + ECS_TRIGGER(world, RemovePosition, EcsOnRemove, Position); + + /* Create new entity with Position. This triggers the OnAdd system. */ + ecs_entity_t e = ecs_new(world, Position); + + /* Remove Position. This will trigger the OnRemove system. */ + ecs_remove(world, e, Position); + + /* Add Position again. This will retrigger the OnAdd system */ + ecs_add(world, e, Position); + + /* Add Position again. This does not retrigger the OnAdd system since the + * entity already has Position */ + ecs_add(world, e, Position); + + /* Cleanup: will invoke OnRemove system */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/06_set_system/include/set_system.h b/fggl/ecs2/flecs/examples/c/06_set_system/include/set_system.h new file mode 100644 index 0000000000000000000000000000000000000000..5c380f1200eab3d96f1d635e9ae75fd1a19ebabf --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/06_set_system/include/set_system.h @@ -0,0 +1,16 @@ +#ifndef SET_SYSTEM_H +#define SET_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "set_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/06_set_system/include/set_system/bake_config.h b/fggl/ecs2/flecs/examples/c/06_set_system/include/set_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..412b0e457c59df88c818771f8fd6d73c2f012876 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/06_set_system/include/set_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SET_SYSTEM_BAKE_CONFIG_H +#define SET_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/06_set_system/project.json b/fggl/ecs2/flecs/examples/c/06_set_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..cef548545bc3496e6c9aad49670b57337c73ec1b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/06_set_system/project.json @@ -0,0 +1,12 @@ +{ + "id": "set_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/06_set_system/src/main.c b/fggl/ecs2/flecs/examples/c/06_set_system/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..18be34cb0118841d7fcfe37c5aff43ad968a2352 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/06_set_system/src/main.c @@ -0,0 +1,58 @@ +#include <set_system.h> + +typedef struct { + double x, y; +} Position; + +/* This system will be called when Position is added */ +void AddPosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + printf("Position added\n"); + } +} + +/* This system will be called when Position is set */ +void SetPosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + printf("Position set -> {%f, %f}\n", + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register Position component */ + ECS_COMPONENT(world, Position); + + /* Register system that is invoked when Position is added */ + ECS_SYSTEM(world, AddPosition, EcsOnAdd, Position); + + /* Register system that is invoked when a value is assigned to Position. + * There are different conditions under which an OnSet system is triggerd. + * This example demonstrates how OnSet is called after an OnAdd system, and + * after calling ecs_set. */ + ECS_SYSTEM(world, SetPosition, EcsOnSet, Position); + + /* Create new entity with Position. Because we have an OnAdd system, flecs + * assumes a valid value will be assigned to Position, and therefore the + * OnSet system is invoked directly after the OnAdd system. */ + ecs_entity_t e = ecs_new(world, Position); + + /* Set Position to a new value (invokes OnSet system) */ + ecs_set(world, e, Position, {20, 30}); + + /* Set Position to a new value again (invokes OnSet system) */ + ecs_set(world, e, Position, {30, 40}); + + /* Cleanup: will invoke OnRemove system */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/07_no_macros/include/no_macros.h b/fggl/ecs2/flecs/examples/c/07_no_macros/include/no_macros.h new file mode 100644 index 0000000000000000000000000000000000000000..b00a56f92d2457eaf855c237813d3e9648f9ca6a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/07_no_macros/include/no_macros.h @@ -0,0 +1,16 @@ +#ifndef NO_MACROS_H +#define NO_MACROS_H + +/* This generated file contains includes for project dependencies */ +#include "no_macros/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/07_no_macros/include/no_macros/bake_config.h b/fggl/ecs2/flecs/examples/c/07_no_macros/include/no_macros/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..c4f33279e38cc058304d0d76f33e9236e3536717 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/07_no_macros/include/no_macros/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 NO_MACROS_BAKE_CONFIG_H +#define NO_MACROS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/07_no_macros/project.json b/fggl/ecs2/flecs/examples/c/07_no_macros/project.json new file mode 100644 index 0000000000000000000000000000000000000000..aead2cb5d58a895bcbcae112dc159190811c8551 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/07_no_macros/project.json @@ -0,0 +1,12 @@ +{ + "id": "no_macros", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/07_no_macros/src/main.c b/fggl/ecs2/flecs/examples/c/07_no_macros/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..65f4cc0ebcc29f7ce11fb63088403b4258f9bc94 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/07_no_macros/src/main.c @@ -0,0 +1,71 @@ +#include <no_macros.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term_w_size(it, sizeof(Position), 1); + Velocity *v = ecs_term_w_size(it, sizeof(Velocity), 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ecs_entity_t pos = ecs_component_init(world, &(ecs_component_desc_t){ + .entity.name = "Position", + .size = sizeof(Position), + .alignment = ECS_ALIGNOF(Position) + }); + + ecs_entity_t vel = ecs_component_init(world, &(ecs_component_desc_t){ + .entity.name = "Velocity", + .size = sizeof(Velocity), + .alignment = ECS_ALIGNOF(Velocity) + }); + + /* Register system */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.terms = {{.id = pos}, {.id = vel}}, + .callback = Move + }); + + /* Create entity with name MyEntity and components position & velocity */ + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "MyEntity", + .add = {pos, vel} + }); + + /* Set values for entity. */ + ecs_set_id(world, e, pos, sizeof(Position), &(Position){0, 0}); + ecs_set_id(world, e, vel, sizeof(Velocity), &(Velocity){1, 1}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/08_hierarchy/include/hierarchy.h b/fggl/ecs2/flecs/examples/c/08_hierarchy/include/hierarchy.h new file mode 100644 index 0000000000000000000000000000000000000000..8fff2d37ce875818d2c3567baed138d55bf86292 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/08_hierarchy/include/hierarchy.h @@ -0,0 +1,16 @@ +#ifndef HIERARCHY_H +#define HIERARCHY_H + +/* This generated file contains includes for project dependencies */ +#include "hierarchy/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/08_hierarchy/include/hierarchy/bake_config.h b/fggl/ecs2/flecs/examples/c/08_hierarchy/include/hierarchy/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..6b3d117ad9c85daa1b828f13a8b8e61219548758 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/08_hierarchy/include/hierarchy/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 HIERARCHY_BAKE_CONFIG_H +#define HIERARCHY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/08_hierarchy/project.json b/fggl/ecs2/flecs/examples/c/08_hierarchy/project.json new file mode 100644 index 0000000000000000000000000000000000000000..0868e7f3ce84e4f997057330490d7cee8948e443 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/08_hierarchy/project.json @@ -0,0 +1,12 @@ +{ + "id": "hierarchy", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/08_hierarchy/src/main.c b/fggl/ecs2/flecs/examples/c/08_hierarchy/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..4e474f4029009dff616d89264715eb33b01a3685 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/08_hierarchy/src/main.c @@ -0,0 +1,122 @@ +#include <hierarchy.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +typedef struct { + double x; + double y; +} WorldPosition; + +typedef struct { + double x, y; +} Velocity; + +typedef double Mass; + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +/* Implement a system that transforms world coordinates hierarchically. If the + * CASCADE column is set, it points to the world coordinate of the parent. This + * will be used to then transform Position to WorldPosition of the child. + * If the CASCADE column is not set, the system matched a root. In that case, + * just assign the Position to the WorldPosition. */ +void Transform(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + WorldPosition *parent_wp = ecs_term(it, WorldPosition, 1); + WorldPosition *wp = ecs_term(it, WorldPosition, 2); + Position *p = ecs_term(it, Position, 3); + + if (!parent_wp) { + for (int i = 0; i < it->count; i ++) { + wp[i].x = p[i].x; + wp[i].y = p[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s transformed to {.x = %f, .y = %f} <<root>>\n", + ecs_get_name(it->world, it->entities[i]), + wp[i].x, wp[i].y); + } + } else { + for (int i = 0; i < it->count; i ++) { + wp[i].x = parent_wp->x + p[i].x; + wp[i].y = parent_wp->y + p[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s transformed to {.x = %f, .y = %f} <<child>>\n", + ecs_get_name(it->world, it->entities[i]), + wp[i].x, wp[i].y); + } + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, WorldPosition); + ECS_COMPONENT(world, Velocity); + + /* Move entities with Position and Velocity */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + /* Transform local coordinates to world coordinates. A CASCADE column + * guarantees that entities are evaluated breadth-first, according to the + * hierarchy. This system will depth-sort based on parents that have the + * WorldPosition component. */ + ECS_SYSTEM(world, Transform, EcsOnUpdate, CASCADE:WorldPosition, WorldPosition, Position); + + /* Create root of the hierachy which moves around */ + ECS_ENTITY(world, Root, WorldPosition, Position, Velocity); + ecs_set(world, Root, Position, {0, 0}); + ecs_set(world, Root, Velocity, {1, 2}); + + /* Create children that don't move and are relative to the parent */ + ECS_ENTITY(world, Child1, WorldPosition, Position, (ChildOf, Root)); + ecs_set(world, Child1, Position, {100, 100}); + + ECS_ENTITY(world, GChild1, WorldPosition, Position, (ChildOf, Root.Child1)); + ecs_set(world, GChild1, Position, {1000, 1000}); + + ECS_ENTITY(world, Child2, WorldPosition, Position, (ChildOf, Root)); + ecs_set(world, Child2, Position, {200, 200}); + + ECS_ENTITY(world, GChild2, WorldPosition, Position, (ChildOf, Root.Child2)); + ecs_set(world, GChild2, Position, {2000, 2000}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)) { + printf("----\n"); + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/09_hierarchy_api/include/hierarchy_api.h b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/include/hierarchy_api.h new file mode 100644 index 0000000000000000000000000000000000000000..a82ba298bbb47dc17fa86ecf154b7ba30300d81b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/include/hierarchy_api.h @@ -0,0 +1,16 @@ +#ifndef HIERARCHY_API_H +#define HIERARCHY_API_H + +/* This generated file contains includes for project dependencies */ +#include "hierarchy_api/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/09_hierarchy_api/include/hierarchy_api/bake_config.h b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/include/hierarchy_api/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..24a7f0d75aef0ee962bd38d87d6bb6f1a7a64a55 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/include/hierarchy_api/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 HIERARCHY_API_BAKE_CONFIG_H +#define HIERARCHY_API_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/09_hierarchy_api/project.json b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f3abb9736a088b00d70286887f6b0434adaa4790 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/project.json @@ -0,0 +1,12 @@ +{ + "id": "hierarchy_api", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/09_hierarchy_api/src/main.c b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..bf21377872f34145627890b0e7a6376765780b14 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/09_hierarchy_api/src/main.c @@ -0,0 +1,131 @@ +#include <hierarchy_api.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +typedef struct { + double x; + double y; +} WorldPosition; + +typedef struct { + double x, y; +} Velocity; + +typedef double Mass; + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +/* Implement a system that transforms world coordinates hierarchically. If the + * CASCADE column is set, it points to the world coordinate of the parent. This + * will be used to then transform Position to WorldPosition of the child. + * If the CASCADE column is not set, the system matched a root. In that case, + * just assign the Position to the WorldPosition. */ +void Transform(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + WorldPosition *parent_wp = ecs_term(it, WorldPosition, 1); + WorldPosition *wp = ecs_term(it, WorldPosition, 2); + Position *p = ecs_term(it, Position, 3); + + if (!parent_wp) { + for (int i = 0; i < it->count; i ++) { + wp[i].x = p[i].x; + wp[i].y = p[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s transformed to {.x = %f, .y = %f} <<root>>\n", + ecs_get_name(it->world, it->entities[i]), + wp[i].x, wp[i].y); + } + } else { + for (int i = 0; i < it->count; i ++) { + wp[i].x = parent_wp->x + p[i].x; + wp[i].y = parent_wp->y + p[i].y; + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s transformed to {.x = %f, .y = %f} <<child>>\n", + ecs_get_name(it->world, it->entities[i]), + wp[i].x, wp[i].y); + } + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, WorldPosition); + ECS_COMPONENT(world, Velocity); + + /* Move entities with Position and Velocity */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + /* Transform local coordinates to world coordinates. A CASCADE column + * guarantees that entities are evaluated breadth-first, according to the + * hierarchy. This system will depth-sort based on parents that have the + * WorldPosition component. */ + ECS_SYSTEM(world, Transform, EcsOnUpdate, CASCADE:WorldPosition, WorldPosition, Position); + + /* Create root of the hierachy which moves around */ + ecs_entity_t Root = ecs_new(world, 0); + ecs_set_name(world, Root, "Root"); + ecs_add(world, Root, WorldPosition); + ecs_set(world, Root, Position, {0, 0}); + ecs_set(world, Root, Velocity, {1, 2}); + + ecs_entity_t Child1 = ecs_new_w_pair(world, EcsChildOf, Root); + ecs_set_name(world, Child1, "Child1"); + ecs_add(world, Child1, WorldPosition); + ecs_set(world, Child1, Position, {100, 100}); + + ecs_entity_t GChild1 = ecs_new_w_pair(world, EcsChildOf, Child1); + ecs_set_name(world, GChild1, "GChild1"); + ecs_add(world, GChild1, WorldPosition); + ecs_set(world, GChild1, Position, {1000, 1000}); + + ecs_entity_t Child2 = ecs_new_w_pair(world, EcsChildOf, Root); + ecs_set_name(world, Child2, "Child2"); + ecs_add(world, Child2, WorldPosition); + ecs_set(world, Child2, Position, {100, 100}); + + ecs_entity_t GChild2 = ecs_new_w_pair(world, EcsChildOf, Child1); + ecs_set_name(world, GChild2, "GChild1"); + ecs_add(world, GChild2, WorldPosition); + ecs_set(world, GChild2, Position, {1000, 1000}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)) { + printf("----\n"); + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/10_inheritance/include/inheritance.h b/fggl/ecs2/flecs/examples/c/10_inheritance/include/inheritance.h new file mode 100644 index 0000000000000000000000000000000000000000..562a166e90c6eae2ba1e7565cb0a164f35ebb763 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/10_inheritance/include/inheritance.h @@ -0,0 +1,16 @@ +#ifndef INHERITANCE_H +#define INHERITANCE_H + +/* This generated file contains includes for project dependencies */ +#include "inheritance/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/10_inheritance/include/inheritance/bake_config.h b/fggl/ecs2/flecs/examples/c/10_inheritance/include/inheritance/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4523aa7a6f35bb9aa37e25155af91acfe340b2d5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/10_inheritance/include/inheritance/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 INHERITANCE_BAKE_CONFIG_H +#define INHERITANCE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/10_inheritance/project.json b/fggl/ecs2/flecs/examples/c/10_inheritance/project.json new file mode 100644 index 0000000000000000000000000000000000000000..79aa103d7bcc1dac143bef6d03522ef0e6704bee --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/10_inheritance/project.json @@ -0,0 +1,12 @@ +{ + "id": "inheritance", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/10_inheritance/src/main.c b/fggl/ecs2/flecs/examples/c/10_inheritance/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f4fb76e9f571dd59a8e8bcd4ec9a0eac21c22fb3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/10_inheritance/src/main.c @@ -0,0 +1,96 @@ +#include <inheritance.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +typedef struct { + double x; + double y; +} Force; + +typedef double Mass; + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term(it, Position, 1); + Force *v = ecs_term(it, Force, 2); + Mass *m = ecs_term(it, Mass, 3); + + for (int i = 0; i < it->count; i ++) { + if (!ecs_is_owned(it, 3)) { + p[i].x += v[i].x / m[0]; + p[i].y += v[i].y / m[0]; + } else { + p[i].x += v[i].x / m[i]; + p[i].y += v[i].y / m[i]; + } + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Force); + ECS_COMPONENT(world, Mass); + + /* Define a system called Move that is executed every frame, and subscribes + * for the 'Position', 'Force' and 'Mass' components. The Mass component + * will be either shared or owned. */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Force, ANY:Mass); + + /* Create two base entities */ + ECS_ENTITY(world, HeavyEntity, Mass); + ecs_set(world, HeavyEntity, Mass, {100}); + + ECS_ENTITY(world, LightEntity, Mass); + ecs_set(world, LightEntity, Mass, {10}); + + /* Create regular entity with Position, Force and Mass */ + ECS_ENTITY(world, Instance0, Position, Force, Mass); + ecs_set(world, Instance0, Position, {0, 0}); + ecs_set(world, Instance0, Force, {10, 10}); + ecs_set(world, Instance0, Mass, {2}); + + /* Create instances which share the Mass component from a base */ + ECS_ENTITY(world, Instance1, Position, Force, (IsA, LightEntity)); + ecs_set(world, Instance1, Position, {0, 0}); + ecs_set(world, Instance1, Force, {10, 10}); + + ECS_ENTITY(world, Instance2, Position, Force, (IsA, LightEntity)); + ecs_set(world, Instance2, Position, {0, 0}); + ecs_set(world, Instance2, Force, {10, 10}); + + ECS_ENTITY(world, Instance3, Position, Force, (IsA, HeavyEntity)); + ecs_set(world, Instance3, Position, {0, 0}); + ecs_set(world, Instance3, Force, {10, 10}); + + ECS_ENTITY(world, Instance4, Position, Force, (IsA, HeavyEntity)); + ecs_set(world, Instance4, Position, {0, 0}); + ecs_set(world, Instance4, Force, {10, 10}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)) { + printf("-----\n"); + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/11_inheritance_api/include/inheritance_api.h b/fggl/ecs2/flecs/examples/c/11_inheritance_api/include/inheritance_api.h new file mode 100644 index 0000000000000000000000000000000000000000..9eb7470a45d3908cd643df70e2d0c940e02e97f9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/11_inheritance_api/include/inheritance_api.h @@ -0,0 +1,16 @@ +#ifndef INHERITANCE_API_H +#define INHERITANCE_API_H + +/* This generated file contains includes for project dependencies */ +#include "inheritance_api/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/11_inheritance_api/include/inheritance_api/bake_config.h b/fggl/ecs2/flecs/examples/c/11_inheritance_api/include/inheritance_api/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..9a09983668859bc2b2dfae65e76423d8e58ab532 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/11_inheritance_api/include/inheritance_api/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 INHERITANCE_API_BAKE_CONFIG_H +#define INHERITANCE_API_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/11_inheritance_api/project.json b/fggl/ecs2/flecs/examples/c/11_inheritance_api/project.json new file mode 100644 index 0000000000000000000000000000000000000000..8d77a987734dcb02dc329a13a67cdf7e18f7f72a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/11_inheritance_api/project.json @@ -0,0 +1,12 @@ +{ + "id": "inheritance_api", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/11_inheritance_api/src/main.c b/fggl/ecs2/flecs/examples/c/11_inheritance_api/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..b7645bb22616fa1747c032717870774e96101494 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/11_inheritance_api/src/main.c @@ -0,0 +1,101 @@ +#include <inheritance_api.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +typedef struct { + double x; + double y; +} Force; + +typedef double Mass; + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term(it, Position, 1); + Force *v = ecs_term(it, Force, 2); + Mass *m = ecs_term(it, Mass, 3); + + for (int i = 0; i < it->count; i ++) { + if (!ecs_is_owned(it, 3)) { + p[i].x += v[i].x / m[0]; + p[i].y += v[i].y / m[0]; + } else { + p[i].x += v[i].x / m[i]; + p[i].y += v[i].y / m[i]; + } + + /* Print something to the console so we can see the system is being + * invoked */ + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Force); + ECS_COMPONENT(world, Mass); + + /* Define a system called Move that is executed every frame, and subscribes + * for the 'Position', 'Force' and 'Mass' components. The Mass component + * will be either shared or owned. */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Force, ANY:Mass); + + /* Create two base entities */ + ecs_entity_t HeavyEntity = ecs_new(world, 0); + ecs_set(world, HeavyEntity, Mass, {100}); + + ecs_entity_t LightEntity = ecs_new(world, 0); + ecs_set(world, LightEntity, Mass, {10}); + + /* Create regular entity with Position, Force and Mass */ + ecs_entity_t Instance0 = ecs_new(world, 0); + ecs_set_name(world, Instance0, "Instance0"); + ecs_set(world, Instance0, Position, {0, 0}); + ecs_set(world, Instance0, Force, {10, 10}); + ecs_set(world, Instance0, Mass, {2}); + + /* Create instances which share the Mass component from a base */ + ecs_entity_t Instance1 = ecs_new_w_pair(world, EcsIsA, LightEntity); + ecs_set_name(world, Instance1, "Instance1"); + ecs_set(world, Instance1, Position, {0, 0}); + ecs_set(world, Instance1, Force, {10, 10}); + + ecs_entity_t Instance2 = ecs_new_w_pair(world, EcsIsA, LightEntity); + ecs_set_name(world, Instance2, "Instance2"); + ecs_set(world, Instance2, Position, {0, 0}); + ecs_set(world, Instance2, Force, {10, 10}); + + ecs_entity_t Instance3 = ecs_new_w_pair(world, EcsIsA, HeavyEntity); + ecs_set_name(world, Instance3, "Instance3"); + ecs_set(world, Instance3, Position, {0, 0}); + ecs_set(world, Instance3, Force, {10, 10}); + + ecs_entity_t Instance4 = ecs_new_w_pair(world, EcsIsA, HeavyEntity); + ecs_set_name(world, Instance4, "Instance4"); + ecs_set(world, Instance4, Position, {0, 0}); + ecs_set(world, Instance4, Force, {10, 10}); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)) { + printf("-----\n"); + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/12_override/include/override.h b/fggl/ecs2/flecs/examples/c/12_override/include/override.h new file mode 100644 index 0000000000000000000000000000000000000000..4260c403b333ed25b106933c658acde396fa2c5b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/12_override/include/override.h @@ -0,0 +1,16 @@ +#ifndef OVERRIDE_H +#define OVERRIDE_H + +/* This generated file contains includes for project dependencies */ +#include "override/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/12_override/include/override/bake_config.h b/fggl/ecs2/flecs/examples/c/12_override/include/override/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b96f6d316537bcaee797522fa4de58dbdd3f69d1 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/12_override/include/override/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OVERRIDE_BAKE_CONFIG_H +#define OVERRIDE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/12_override/project.json b/fggl/ecs2/flecs/examples/c/12_override/project.json new file mode 100644 index 0000000000000000000000000000000000000000..49ef377d00f390747c6d4b65cdfa1aeef75792ac --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/12_override/project.json @@ -0,0 +1,12 @@ +{ + "id": "override", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/12_override/src/main.c b/fggl/ecs2/flecs/examples/c/12_override/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..9000d093191a56e085730f343264700dc844f338 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/12_override/src/main.c @@ -0,0 +1,47 @@ +#include <override.h> + +/* Component types */ +typedef double Mass; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Mass); + + /* Create base entity */ + ecs_entity_t base = ecs_new(world, 0); + ecs_set(world, base, Mass, {10}); + + /* Create instances which share the Mass component from a base */ + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + + /* Print value before overriding Mass. The component is not owned, as it is + * shared with the base entity. */ + printf("Before overriding:\n"); + printf("instance: %f (owned = %u)\n", *ecs_get(world, instance, Mass), ecs_owns(world, instance, Mass, true)); + + /* Override Mass of instance, which will give instance a private copy of the + * Mass component. */ + ecs_set(world, instance, Mass, {20}); + + /* Print values after overriding Mass. The value of Mass for instance_1 will + * be the value assigned in the override (20). Instance now owns Mass, + * confirming it has a private copy of the component. */ + printf("\nAfter overriding:\n"); + printf("instance: %f (owned = %u)\n", *ecs_get(world, instance, Mass), ecs_owns(world, instance, Mass, true)); + + /* Remove override of Mass. This will remove the private copy of the Mass + * component from instance. */ + ecs_remove(world, instance, Mass); + + /* Print value after removing the override Mass. The component is no longer + * owned, as the instance is again sharing the component with base. */ + printf("\nAfter removing override:\n"); + printf("instance: %f (owned = %u)\n", *ecs_get(world, instance, Mass), ecs_owns(world, instance, Mass, true)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/13_override_init/include/override_init.h b/fggl/ecs2/flecs/examples/c/13_override_init/include/override_init.h new file mode 100644 index 0000000000000000000000000000000000000000..ce64a1f727c6cdac48a5d61b334b901c12c9f18c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/13_override_init/include/override_init.h @@ -0,0 +1,16 @@ +#ifndef OVERRIDE_INIT_H +#define OVERRIDE_INIT_H + +/* This generated file contains includes for project dependencies */ +#include "override_init/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/13_override_init/include/override_init/bake_config.h b/fggl/ecs2/flecs/examples/c/13_override_init/include/override_init/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..aeceda3d8e7a8637cf34dd2d163ab116761b9a64 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/13_override_init/include/override_init/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OVERRIDE_INIT_BAKE_CONFIG_H +#define OVERRIDE_INIT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/13_override_init/project.json b/fggl/ecs2/flecs/examples/c/13_override_init/project.json new file mode 100644 index 0000000000000000000000000000000000000000..04fa79650682d7feb10e47b45b9b2f217e03af83 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/13_override_init/project.json @@ -0,0 +1,12 @@ +{ + "id": "override_init", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/13_override_init/src/main.c b/fggl/ecs2/flecs/examples/c/13_override_init/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..9dcc73a96a3797cd53246cfb794cba91a74f0873 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/13_override_init/src/main.c @@ -0,0 +1,46 @@ +#include <override_init.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +typedef struct { + double x; + double y; +} Force; + +typedef double Mass; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Mass); + + /* Create base entity. Create entity as disabled, which will prevent it from + * being matched with systems. This is a common approach to creating + * entities that are only used as templates for other entities, or in this + * case, for providing initial component values. */ + ecs_entity_t base = ecs_new_w_entity(world, EcsDisabled); + ecs_set(world, base, Mass, {10}); + + /* Create instances which share the Mass component from a base */ + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + + /* Add component without setting it. This will initialize the new component + * with the value from the base, which is a common approach to initializing + * components with a value when they are added. */ + ecs_add(world, instance, Mass); + + /* Print value of mass. The value will be equal to base, and the instance + * will own the component. */ + printf("instance: %f (owned = %u)\n", + *ecs_get(world, instance, Mass), + ecs_owns(world, instance, Mass, true)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/14_add_type/include/add_type.h b/fggl/ecs2/flecs/examples/c/14_add_type/include/add_type.h new file mode 100644 index 0000000000000000000000000000000000000000..774f07b40fe7b35272c2a8dfb74fa4928b976b24 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/14_add_type/include/add_type.h @@ -0,0 +1,16 @@ +#ifndef ADD_TYPE_H +#define ADD_TYPE_H + +/* This generated file contains includes for project dependencies */ +#include "add_type/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/14_add_type/include/add_type/bake_config.h b/fggl/ecs2/flecs/examples/c/14_add_type/include/add_type/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..1e59b177b88c1d10271d7ee55b44fea95cb6b229 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/14_add_type/include/add_type/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 ADD_TYPE_BAKE_CONFIG_H +#define ADD_TYPE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/14_add_type/project.json b/fggl/ecs2/flecs/examples/c/14_add_type/project.json new file mode 100644 index 0000000000000000000000000000000000000000..b8567635308d115ea688d116b7377ea096cfa56f --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/14_add_type/project.json @@ -0,0 +1,12 @@ +{ + "id": "add_type", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/14_add_type/src/main.c b/fggl/ecs2/flecs/examples/c/14_add_type/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..a4f47655c81fc30949569a34786c7c710a7bd31e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/14_add_type/src/main.c @@ -0,0 +1,41 @@ +#include <add_type.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create a type with both Position and Velocity. This allows applications + * to create entities with a specific type, or add multiple components in a + * single operation. This is much more efficient than adding each component + * individually to an entity */ + ECS_TYPE(world, Movable, Position, Velocity); + + /* Create new entity with type */ + ecs_entity_t e = ecs_new(world, Movable); + + /* Test if entity has the components */ + printf("After new with type:\n"); + printf("Has Position? %u\n", ecs_has(world, e, Position)); + printf("Has Velocity? %u\n", ecs_has(world, e, Velocity)); + + /* Remove both components in one operation */ + ecs_remove(world, e, Movable); + + /* Test if entity has the components */ + printf("\nAfter remove with type:\n"); + printf("Has Position? %u\n", ecs_has(world, e, Position)); + printf("Has Velocity? %u\n", ecs_has(world, e, Velocity)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/15_auto_override/include/auto_override.h b/fggl/ecs2/flecs/examples/c/15_auto_override/include/auto_override.h new file mode 100644 index 0000000000000000000000000000000000000000..9884a68966ecc5675dd35f7e784be832fea61589 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/15_auto_override/include/auto_override.h @@ -0,0 +1,16 @@ +#ifndef AUTO_OVERRIDE_H +#define AUTO_OVERRIDE_H + +/* This generated file contains includes for project dependencies */ +#include "auto_override/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/15_auto_override/include/auto_override/bake_config.h b/fggl/ecs2/flecs/examples/c/15_auto_override/include/auto_override/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..e9beb630d3456be544934687c097d78794300641 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/15_auto_override/include/auto_override/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 AUTO_OVERRIDE_BAKE_CONFIG_H +#define AUTO_OVERRIDE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/15_auto_override/project.json b/fggl/ecs2/flecs/examples/c/15_auto_override/project.json new file mode 100644 index 0000000000000000000000000000000000000000..c6a1841dbb7431b4eadfcc831f43a5f35c42ab49 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/15_auto_override/project.json @@ -0,0 +1,12 @@ +{ + "id": "auto_override", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/15_auto_override/src/main.c b/fggl/ecs2/flecs/examples/c/15_auto_override/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..8434a4a1254f8d467598197be5b957a66c6a90a0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/15_auto_override/src/main.c @@ -0,0 +1,44 @@ +#include <auto_override.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create entity with default values for Position and Velocity. Add the + * EcsDisabled tag to ensure the entity will not be matched by systems, + * since it is only used to provide initial component values. */ + ECS_ENTITY(world, Base, Position, Velocity, EcsDisabled); + ecs_set(world, Base, Position, {10, 20}); + ecs_set(world, Base, Velocity, {30, 40}); + + /* This type inherits from Base, as well as adding Position and Velocity as + * private components. This will automatically override the components as an + * entity with this type is created, which will initialize the private + * values with the values of the Base entity. This is a common approach to + * creating entities with an initialized set of components. */ + ECS_TYPE(world, Movable, (IsA, Base), Position, Velocity); + + /* Create new entity with type */ + ecs_entity_t e = ecs_new(world, Movable); + + /* Get pointers to component values */ + const Position *p = ecs_get(world, e, Position); + const Velocity *v = ecs_get(world, e, Velocity); + + /* Print values of entity */ + printf("Position: {%f, %f} (owned = %d)\n", p->x, p->y, ecs_owns(world, e, Position, true)); + printf("Velocity: {%f, %f} (owned = %d)\n", v->x, v->y, ecs_owns(world, e, Velocity, true)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/16_prefab/include/prefab.h b/fggl/ecs2/flecs/examples/c/16_prefab/include/prefab.h new file mode 100644 index 0000000000000000000000000000000000000000..31c61ff328348ab526778aa7b75d7a6e56861b2b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/16_prefab/include/prefab.h @@ -0,0 +1,16 @@ +#ifndef PREFAB_H +#define PREFAB_H + +/* This generated file contains includes for project dependencies */ +#include "prefab/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/16_prefab/include/prefab/bake_config.h b/fggl/ecs2/flecs/examples/c/16_prefab/include/prefab/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d091f5b8c16de8e9228d540333e1056e55252a24 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/16_prefab/include/prefab/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PREFAB_BAKE_CONFIG_H +#define PREFAB_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/16_prefab/project.json b/fggl/ecs2/flecs/examples/c/16_prefab/project.json new file mode 100644 index 0000000000000000000000000000000000000000..804b9fa9e134393b48532a63cebaea68fafb46e4 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/16_prefab/project.json @@ -0,0 +1,12 @@ +{ + "id": "prefab", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/16_prefab/src/main.c b/fggl/ecs2/flecs/examples/c/16_prefab/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..8c9614ae2ea02f92f44809eac728d1ca7dbbf723 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/16_prefab/src/main.c @@ -0,0 +1,44 @@ +#include <prefab.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create a prefab. Prefabs are entities that are solely intended as + * templates for other entities. Prefabs are by default not matched with + * systems. In that way they are similar to regular entities with the + * EcsDisbled tag, except that they have more features which are + * demonstrated in the nested_prefab example. */ + ECS_PREFAB(world, BasePrefab, Position, Velocity); + ecs_set(world, BasePrefab, Position, {10, 20}); + ecs_set(world, BasePrefab, Velocity, {30, 40}); + + /* Use the same technique as used in the auto_override example to create a + * type that causes components to be automatically overriden from the base. + * Note that prefabs use inheritance. */ + ECS_TYPE(world, Base, (IsA, BasePrefab), Position, Velocity); + + /* Create new entity with type */ + ecs_entity_t e = ecs_new(world, Base); + + /* Get pointers to component values */ + const Position *p = ecs_get(world, e, Position); + const Velocity *v = ecs_get(world, e, Velocity); + + /* Print values of entity */ + printf("Position: {%f, %f} (owned = %d)\n", p->x, p->y, ecs_owns(world, e, Position, true)); + printf("Velocity: {%f, %f} (owned = %d)\n", v->x, v->y, ecs_owns(world, e, Velocity, true)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/17_prefab_variant/include/prefab_variant.h b/fggl/ecs2/flecs/examples/c/17_prefab_variant/include/prefab_variant.h new file mode 100644 index 0000000000000000000000000000000000000000..44af11855d080c750ec7335e535b242be51b6d87 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/17_prefab_variant/include/prefab_variant.h @@ -0,0 +1,16 @@ +#ifndef PREFAB_VARIANT_H +#define PREFAB_VARIANT_H + +/* This generated file contains includes for project dependencies */ +#include "prefab_variant/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/17_prefab_variant/include/prefab_variant/bake_config.h b/fggl/ecs2/flecs/examples/c/17_prefab_variant/include/prefab_variant/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d69de54b7bd1dc2a8f461766f811ba95f1fe1e1c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/17_prefab_variant/include/prefab_variant/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PREFAB_VARIANT_BAKE_CONFIG_H +#define PREFAB_VARIANT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/17_prefab_variant/project.json b/fggl/ecs2/flecs/examples/c/17_prefab_variant/project.json new file mode 100644 index 0000000000000000000000000000000000000000..e81fe085496acf214e097e3fc7856bf0c64d30b9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/17_prefab_variant/project.json @@ -0,0 +1,12 @@ +{ + "id": "prefab_variant", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/17_prefab_variant/src/main.c b/fggl/ecs2/flecs/examples/c/17_prefab_variant/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..517306618331fbccd065a4b98c8959bb864bf518 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/17_prefab_variant/src/main.c @@ -0,0 +1,52 @@ +#include <prefab_variant.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create a BasePrefab from which will be specialized by other prefabs */ + ECS_PREFAB(world, BasePrefab, Position); + ecs_set(world, BasePrefab, Position, {10, 20}); + + /* Create two prefab specializations. This uses the same inheritance + * mechanism as is used with regular entities. */ + ECS_PREFAB(world, SubPrefab1, (IsA, BasePrefab), Velocity); + ecs_set(world, SubPrefab1, Velocity, {30, 40}); + + ECS_PREFAB(world, SubPrefab2, (IsA, BasePrefab), Velocity); + ecs_set(world, SubPrefab2, Velocity, {50, 60}); + + /* Create two types for SubPrefab1 and SubPrefab2 which automatically + * override the component values. The Position component will be overridden + * from the BasePrefab, while Velocity will be overridden from SubPrefab1 + * and SubPrefab2 respectively. */ + ECS_TYPE(world, Sub1, (IsA, SubPrefab1), Position, Velocity); + ECS_TYPE(world, Sub2, (IsA, SubPrefab2), Position, Velocity); + + /* Create new entities from Sub1 and Sub2 */ + ecs_entity_t e1 = ecs_new(world, Sub1); + ecs_entity_t e2 = ecs_new(world, Sub2); + + /* Print values of e1 */ + const Position *p = ecs_get(world, e1, Position); + const Velocity *v = ecs_get(world, e1, Velocity); + printf("e1 Position: {%f, %f} Velocity: {%f, %f}\n", p->x, p->y, v->x, v->y); + + /* Print values of e2 */ + p = ecs_get(world, e2, Position); + v = ecs_get(world, e2, Velocity); + printf("e2 Position: {%f, %f} Velocity: {%f, %f}\n", p->x, p->y, v->x, v->y); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/18_nested_prefab/include/nested_prefab.h b/fggl/ecs2/flecs/examples/c/18_nested_prefab/include/nested_prefab.h new file mode 100644 index 0000000000000000000000000000000000000000..cae5634ea79fdcec260f4a37e93c9c2494f1df63 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/18_nested_prefab/include/nested_prefab.h @@ -0,0 +1,16 @@ +#ifndef NESTED_PREFAB_H +#define NESTED_PREFAB_H + +/* This generated file contains includes for project dependencies */ +#include "nested_prefab/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/18_nested_prefab/include/nested_prefab/bake_config.h b/fggl/ecs2/flecs/examples/c/18_nested_prefab/include/nested_prefab/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..1df0d22038454a03eef001d58a86e1bf3306f3a2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/18_nested_prefab/include/nested_prefab/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 NESTED_PREFAB_BAKE_CONFIG_H +#define NESTED_PREFAB_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/18_nested_prefab/project.json b/fggl/ecs2/flecs/examples/c/18_nested_prefab/project.json new file mode 100644 index 0000000000000000000000000000000000000000..00b4bfad8f880d75127289e36d50d67d34fe74f6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/18_nested_prefab/project.json @@ -0,0 +1,12 @@ +{ + "id": "nested_prefab", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/18_nested_prefab/src/main.c b/fggl/ecs2/flecs/examples/c/18_nested_prefab/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..576a236a467c5260a39e4fe8a5287d0268baa82c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/18_nested_prefab/src/main.c @@ -0,0 +1,47 @@ +#include <nested_prefab.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + + /* Create a prefab with a child entity. When this prefab is instantiated, + * the child will be instantiated too as a child of the instance. */ + ECS_PREFAB(world, RootPrefab, Position); + ecs_set(world, RootPrefab, Position, {10, 20}); + + /* The child needs to explicitly set the parent in the EcsPrefab + * component. This is needed for Flecs to register the child with the + * parent prefab. */ + ECS_PREFAB(world, Child, (ChildOf, RootPrefab), Position); + ecs_set(world, Child, Position, {30, 40}); + + /* Create instance of root */ + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, RootPrefab); + + /* Lookup child in the instance we just created. This child will have e in + * its type with a ChildOf pair, and the prefab Child in its type with an + * IsA pair. */ + ecs_entity_t child = ecs_lookup_child(world, e, "Child"); + printf("Child type = [%s]\n", ecs_type_str(world, ecs_get_type(world, child))); + + /* Print position of e and of the child. Note that since we did not override + * any components, both position components are owned by the prefabs, not by + * the instances. */ + const Position *p = ecs_get(world, e, Position); + printf("Position of e = {%f, %f}\n", p->x, p->y); + + p = ecs_get(world, child, Position); + printf("Position of Child = {%f, %f}\n", p->x, p->y); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/include/auto_override_nested_prefab.h b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/include/auto_override_nested_prefab.h new file mode 100644 index 0000000000000000000000000000000000000000..c4c9a1a625b66afd7042ac54c29e28f8a412eaea --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/include/auto_override_nested_prefab.h @@ -0,0 +1,16 @@ +#ifndef AUTO_OVERRIDE_NESTED_PREFAB_H +#define AUTO_OVERRIDE_NESTED_PREFAB_H + +/* This generated file contains includes for project dependencies */ +#include "auto_override_nested_prefab/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/include/auto_override_nested_prefab/bake_config.h b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/include/auto_override_nested_prefab/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..f3dc4baaf44852a59b1b59db24df4065fc2d8bd3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/include/auto_override_nested_prefab/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 AUTO_OVERRIDE_NESTED_PREFAB_BAKE_CONFIG_H +#define AUTO_OVERRIDE_NESTED_PREFAB_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/project.json b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/project.json new file mode 100644 index 0000000000000000000000000000000000000000..fe135740607938917307124fe2eb9fbce28f43a8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/project.json @@ -0,0 +1,12 @@ +{ + "id": "auto_override_nested_prefab", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/src/main.c b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..7dc9c0dbc582fdf927005974bf938ce7ff2c88a2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/19_auto_override_nested_prefab/src/main.c @@ -0,0 +1,56 @@ +#include <auto_override_nested_prefab.h> + +/* Component types */ +typedef struct { + double x, y; +} Position; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + + /* Create root prefab */ + ECS_PREFAB(world, RootPrefab, Position); + ecs_set(world, RootPrefab, Position, {10, 20}); + + /* Create a standalone ChildPrefab that is not a child of RootPrefab. + * This will cause it to not be instantiated when RootPrefab is + * is instantiated. */ + ECS_PREFAB(world, ChildPrefab, Position); + ecs_set(world, ChildPrefab, Position, {30, 40}); + + /* Create a prefab that is an instance of ChildPrefab that overrides the + * Position component. When RootPrefab is instantiated, it will have a + * child with the exact same type as Child, meaning that it will + * automatically override Position. */ + ECS_PREFAB(world, Child, (ChildOf, RootPrefab), (IsA, ChildPrefab), Position); + + /* Create type that automatically overrides Position from RootPrefab */ + ECS_TYPE(world, Root, (IsA, RootPrefab), Position); + + /* Create new entity from Root. Don't use EcsIsA, as we're using a + * regular type which already has the IsA relationship. */ + ecs_entity_t e = ecs_new(world, Root); + + /* Lookup child in the instance we just created. This child will have e in + * its type with a ChildOf pair, and the prefab ChildPrefab in its type with + * an IsA pair. Note how the identifier is Child, not ChildPrefab. */ + ecs_entity_t child = ecs_lookup_child(world, e, "Child"); + printf("Child type = [%s]\n", ecs_type_str(world, ecs_get_type(world, child))); + + /* Print position of e and of the child. Note that since types were used to + * automatically override the components, the components are owned by both + * e and child. */ + const Position *p = ecs_get(world, e, Position); + printf("Position of e = {%f, %f} (owned = %d)\n", p->x, p->y, ecs_owns(world, e, Position, true)); + + p = ecs_get(world, child, Position); + printf("Position of Child = {%f, %f} (owned = %d)\n", p->x, p->y, ecs_owns(world, child, Position, true)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/20_nested_variant/include/nested_variant.h b/fggl/ecs2/flecs/examples/c/20_nested_variant/include/nested_variant.h new file mode 100644 index 0000000000000000000000000000000000000000..61af956cbf1ee5a01dad62de0fcd00b0c91076fb --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/20_nested_variant/include/nested_variant.h @@ -0,0 +1,16 @@ +#ifndef NESTED_VARIANT_H +#define NESTED_VARIANT_H + +/* This generated file contains includes for project dependencies */ +#include "nested_variant/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/20_nested_variant/include/nested_variant/bake_config.h b/fggl/ecs2/flecs/examples/c/20_nested_variant/include/nested_variant/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..22ec5839bef46ea3c9d5d597dfe6727d19cea092 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/20_nested_variant/include/nested_variant/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 NESTED_VARIANT_BAKE_CONFIG_H +#define NESTED_VARIANT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/20_nested_variant/project.json b/fggl/ecs2/flecs/examples/c/20_nested_variant/project.json new file mode 100644 index 0000000000000000000000000000000000000000..82429f95dcda539eb6bd7f93013877aa6b482e08 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/20_nested_variant/project.json @@ -0,0 +1,12 @@ +{ + "id": "nested_variant", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/20_nested_variant/src/main.c b/fggl/ecs2/flecs/examples/c/20_nested_variant/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f7eabf1f20542fb6d21bc59f7ded8228a7053531 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/20_nested_variant/src/main.c @@ -0,0 +1,58 @@ +#include <nested_variant.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create a base prefab which will be inherited from by a child prefab */ + ECS_PREFAB(world, BasePrefab, Position); + ecs_set(world, BasePrefab, Position, {10, 20}); + + /* Create the root of the prefab hierarchy */ + ECS_PREFAB(world, RootPrefab, Position); + ecs_set(world, RootPrefab, Position, {10, 20}); + + /* Create two child prefabs that inherit from BasePrefab */ + ECS_PREFAB(world, Child1, (ChildOf, RootPrefab), (IsA, BasePrefab), Velocity); + ecs_set(world, Child1, Velocity, {30, 40}); + + ECS_PREFAB(world, Child2, (ChildOf, RootPrefab), (IsA, BasePrefab), Velocity); + ecs_set(world, Child2, Velocity, {50, 60}); + + /* Create instance of RootPrefab */ + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, RootPrefab); + + /* Print types of child1 and child2 */ + ecs_entity_t child1 = ecs_lookup_child(world, e, "Child1"); + printf("Child1 type = [%s]\n", ecs_type_str(world, ecs_get_type(world, child1))); + + ecs_entity_t child2 = ecs_lookup_child(world, e, "Child2"); + printf("Child2 type = [%s]\n", ecs_type_str(world, ecs_get_type(world, child2))); + + /* e shares Position from RootPrefab */ + const Position *p = ecs_get(world, e, Position); + printf("Position of e = {%f, %f}\n", p->x, p->y); + + /* Children will share Position from ChildBase and Velocity from the Child1 + * and Child2 prefabs respectively */ + p = ecs_get(world, child1, Position); + const Velocity *v = ecs_get(world, child1, Velocity); + printf("Child1 Position = {%f, %f}, Velocity = {%f, %f}\n", p->x, p->y, v->x, v->y); + + p = ecs_get(world, child2, Position); + v = ecs_get(world, child2, Velocity); + printf("Child2 Position = {%f, %f}, Velocity = {%f, %f}\n", p->x, p->y, v->x, v->y); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/21_system_features/include/system_features.h b/fggl/ecs2/flecs/examples/c/21_system_features/include/system_features.h new file mode 100644 index 0000000000000000000000000000000000000000..ba4bb2c960286d944ea1b6153c2fe02c8aa87b28 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/21_system_features/include/system_features.h @@ -0,0 +1,16 @@ +#ifndef SYSTEM_FEATURES_H +#define SYSTEM_FEATURES_H + +/* This generated file contains includes for project dependencies */ +#include "system_features/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/21_system_features/include/system_features/bake_config.h b/fggl/ecs2/flecs/examples/c/21_system_features/include/system_features/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4a326c0bd05f44639f497d8ad9532b005713f215 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/21_system_features/include/system_features/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SYSTEM_FEATURES_BAKE_CONFIG_H +#define SYSTEM_FEATURES_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/21_system_features/project.json b/fggl/ecs2/flecs/examples/c/21_system_features/project.json new file mode 100644 index 0000000000000000000000000000000000000000..4edf14a23865023d58fc87b498c52d6b6a5ccc2e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/21_system_features/project.json @@ -0,0 +1,12 @@ +{ + "id": "system_features", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} diff --git a/fggl/ecs2/flecs/examples/c/21_system_features/src/main.c b/fggl/ecs2/flecs/examples/c/21_system_features/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..edf3e6983229ddc37691930d30d6ef9a09e2b0d3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/21_system_features/src/main.c @@ -0,0 +1,79 @@ +#include <system_features.h> + +void SystemA(ecs_iter_t *it) { + (void)it; // Unused + printf("System A called!\n"); +} + +void SystemB(ecs_iter_t *it) { + (void)it; // Unused + printf("System B called!\n"); +} + +void SystemC(ecs_iter_t *it) { + (void)it; // Unused + printf("System C called!\n"); +} + +void SystemD(ecs_iter_t *it) { + (void)it; // Unused + printf("System D called!\n"); +} + +void SystemE(ecs_iter_t *it) { + (void)it; // Unused + printf("System E called!\n"); +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Create dummy systems */ + ECS_SYSTEM(world, SystemA, EcsOnUpdate, 0); + ECS_SYSTEM(world, SystemB, EcsOnUpdate, 0); + ECS_SYSTEM(world, SystemC, EcsOnUpdate, 0); + ECS_SYSTEM(world, SystemD, EcsOnUpdate, 0); + ECS_SYSTEM(world, SystemE, EcsOnUpdate, 0); + + /* Create two features, each with a set of systems. Features are regular + * types, and the name feature is just a convention to indicate that a type + * only contains systems. Since systems, just like components, are stored as + * entities, they can be contained by types. */ + ECS_TYPE(world, Feature1, SystemA, SystemB); + ECS_TYPE(world, Feature2, SystemC, SystemD); + + /* Create a feature that includes Feature2 and SystemE. Types/features can + * be organized in hierarchies */ + ECS_TYPE(world, Feature3, Feature2, SystemE); + + /* First, disable Feature1 and Feature3. No systems will be executed. */ + printf("Feature1 disabled, Feature3 disabled:\n"); + ecs_enable(world, Feature1, false); + ecs_enable(world, Feature3, false); + ecs_progress(world, 1); + + /* Enable Feature3 */ + printf("\nFeature1 disabled, Feature3 enabled:\n"); + ecs_enable(world, Feature3, true); + ecs_progress(world, 1); + + /* Disable Feature2. This will disable some of the systems in Feature3 too */ + printf("\nFeature1 disabled, Feature3 partially enabled:\n"); + ecs_enable(world, Feature2, false); + ecs_progress(world, 1); + + /* Enable Feature1 */ + printf("\nFeature1 enabled, Feature3 partially enabled:\n"); + ecs_enable(world, Feature1, true); + ecs_progress(world, 1); + + /* Disable SystemE */ + printf("\nDisable SystemE:\n"); + ecs_enable(world, SystemE, false); + ecs_progress(world, 1); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/22_optional/include/optional.h b/fggl/ecs2/flecs/examples/c/22_optional/include/optional.h new file mode 100644 index 0000000000000000000000000000000000000000..1629d71273cbbeba44dcd2b8bca31b4187e46521 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/22_optional/include/optional.h @@ -0,0 +1,16 @@ +#ifndef OPTIONAL_H +#define OPTIONAL_H + +/* This generated file contains includes for project dependencies */ +#include "optional/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/22_optional/include/optional/bake_config.h b/fggl/ecs2/flecs/examples/c/22_optional/include/optional/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..08e1eb492a390264161617c9e45d87c4b9858979 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/22_optional/include/optional/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OPTIONAL_BAKE_CONFIG_H +#define OPTIONAL_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/22_optional/project.json b/fggl/ecs2/flecs/examples/c/22_optional/project.json new file mode 100644 index 0000000000000000000000000000000000000000..39fba064091fcb99565da7a4d08baed78c3bbbc5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/22_optional/project.json @@ -0,0 +1,12 @@ +{ + "id": "optional", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/22_optional/src/main.c b/fggl/ecs2/flecs/examples/c/22_optional/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..86b0bd1993f31e46b9124b9c88a3a70b669601b4 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/22_optional/src/main.c @@ -0,0 +1,51 @@ +#include <optional.h> + +typedef double Health; +typedef double Stamina; +typedef double Mana; + +void Regenerate(ecs_iter_t *it) { + Health *health = ecs_term(it, Health, 1); + Stamina *stamina = ecs_term(it, Stamina, 2); + Mana *mana = ecs_term(it, Mana, 3); + + for (int i = 0; i < it->count; i ++) { + if (health) { + health[i] ++; + printf("%d: process health\n", (int)it->entities[i]); + } + + if (stamina) { + stamina[i] ++; + printf("%d: process stamina\n", (int)it->entities[i]); + } + + if (mana) { + mana[i] ++; + printf("%d: process mana\n", (int)it->entities[i]); + } + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Health); + ECS_COMPONENT(world, Stamina); + ECS_COMPONENT(world, Mana); + + ECS_SYSTEM(world, Regenerate, EcsOnUpdate, ?Health, ?Stamina, ?Mana); + + /* Create three entities that will all match with the Regenerate system */ + ecs_new(world, Health); + ecs_new(world, Stamina); + ecs_new(world, Mana); + + /* Run systems */ + ecs_progress(world, 0); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/23_get_children/include/get_children.h b/fggl/ecs2/flecs/examples/c/23_get_children/include/get_children.h new file mode 100644 index 0000000000000000000000000000000000000000..b57331fedb9ba7e4d93124e2b18647cfef79eaf0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/23_get_children/include/get_children.h @@ -0,0 +1,16 @@ +#ifndef GET_CHILDREN_H +#define GET_CHILDREN_H + +/* This generated file contains includes for project dependencies */ +#include "get_children/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/23_get_children/include/get_children/bake_config.h b/fggl/ecs2/flecs/examples/c/23_get_children/include/get_children/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..11cf900af96af3898ee9f5a7b39ea68f8cb392b2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/23_get_children/include/get_children/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 GET_CHILDREN_BAKE_CONFIG_H +#define GET_CHILDREN_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/23_get_children/project.json b/fggl/ecs2/flecs/examples/c/23_get_children/project.json new file mode 100644 index 0000000000000000000000000000000000000000..3f182cfbde35cd1d878aabd73f328bc2ecbefbcb --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/23_get_children/project.json @@ -0,0 +1,12 @@ +{ + "id": "get_children", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/23_get_children/src/main.c b/fggl/ecs2/flecs/examples/c/23_get_children/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..57e827340a8be965f6aa027395e76b16b45c840a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/23_get_children/src/main.c @@ -0,0 +1,40 @@ +#include <get_children.h> + +int indent = 0; + +void print_tree( + ecs_world_t *world, + ecs_entity_t entity) +{ + printf("%*s%s\n", indent * 2, "", ecs_get_name(world, entity)); + + indent ++; + + // Get an iterator to the children of the current entity + ecs_iter_t it = ecs_scope_iter(world, entity); + + // Iterate all the tables that contain children for the parent + while (ecs_scope_next(&it)) { + for (int i = 0; i < it.count; i ++) { + // Print the child, and recursively iterate + print_tree(world, it.entities[i]); + } + } + + indent --; +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + // Create a simple hierarchy with 2 levels + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child1, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child1)); + ECS_ENTITY(world, Child2, (ChildOf, Parent)); + ECS_ENTITY(world, Child3, (ChildOf, Parent)); + + print_tree(world, Parent); + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/24_on_demand_system/include/on_demand_system.h b/fggl/ecs2/flecs/examples/c/24_on_demand_system/include/on_demand_system.h new file mode 100644 index 0000000000000000000000000000000000000000..0940bf26f9742e8684d8a20b93fc79075d936920 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/24_on_demand_system/include/on_demand_system.h @@ -0,0 +1,16 @@ +#ifndef ON_DEMAND_SYSTEM_H +#define ON_DEMAND_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "on_demand_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/24_on_demand_system/include/on_demand_system/bake_config.h b/fggl/ecs2/flecs/examples/c/24_on_demand_system/include/on_demand_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b97d48c43594398ddd5856b926ee2ece76258680 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/24_on_demand_system/include/on_demand_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 ON_DEMAND_SYSTEM_BAKE_CONFIG_H +#define ON_DEMAND_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/24_on_demand_system/project.json b/fggl/ecs2/flecs/examples/c/24_on_demand_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..53fea9ca1130f8490aff04d0baa1921234d6eec6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/24_on_demand_system/project.json @@ -0,0 +1,12 @@ +{ + "id": "on_demand_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/24_on_demand_system/src/main.c b/fggl/ecs2/flecs/examples/c/24_on_demand_system/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..d47f87482554bd12e7a6c4747a44da4949feceb8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/24_on_demand_system/src/main.c @@ -0,0 +1,75 @@ +#include <on_demand_system.h> + +typedef struct { + double x, y; +} Position, Velocity; + +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + printf("Move {%f, %f}\n", p[i].x, p[i].y); + } +} + +void PrintPosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + printf("Print {%f, %f}\n", p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, Printable); + + /* The 'Move' system has the 'EcsOnDemand' tag which means Flecs will only + * run this system if there is interest in any of its [out] columns. In this + * case the system will only be ran if there is interest in Position. */ + ECS_SYSTEM(world, Move, EcsOnUpdate, [out] Position, Velocity, SYSTEM:EcsOnDemand); + + /* The 'PrintPosition' is a regular system with an [in] column. This signals + * that the system will not write Position, and relies on another system to + * provide a value for it. If there are any OnDemand systems that provide + * 'Position' as an output, they will be enabled. */ + ECS_SYSTEM(world, PrintPosition, EcsOnUpdate, [in] Position, Printable); + + /* Create entity, set components */ + ecs_entity_t e = + ecs_set(world, 0, Position, {10, 20}); + ecs_set(world, e, Velocity, {1, 2}); + + /* No systems will be executed. The PrintPosition system is enabled, but it + * has no matching entities. As a result, there is no demand for the + * Position component, and the Move system won't be executed either, even + * though the entity does match with it. */ + printf("First iteration: PrintPosition is inactive\n"); + ecs_progress(world, 0); + + /* Add Printable to the entity */ + ecs_add(world, e, Printable); + + /* Both systems will now be executed. The entity matches with PrintPosition + * meaning there is demand for Position, and thus the Move system will be + * enabled. */ + printf("\nSecond iteration: PrintPosition is active\n"); + ecs_progress(world, 0); + + /* Disable the PrintPosition system. Now there is no longer demand for the + * Position component, so the Move on-demand system will be disabled. */ + printf("\nThird iteration: PrintPosition is disabled\n"); + ecs_enable(world, PrintPosition, false); + ecs_progress(world, 0); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/25_snapshot/include/snapshot.h b/fggl/ecs2/flecs/examples/c/25_snapshot/include/snapshot.h new file mode 100644 index 0000000000000000000000000000000000000000..5a1b0ca48b326564f87cbfdde0680bb7ab08889e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/25_snapshot/include/snapshot.h @@ -0,0 +1,16 @@ +#ifndef SNAPSHOT_H +#define SNAPSHOT_H + +/* This generated file contains includes for project dependencies */ +#include "snapshot/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/25_snapshot/include/snapshot/bake_config.h b/fggl/ecs2/flecs/examples/c/25_snapshot/include/snapshot/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..8790fd39d2ebee904ea7eb803e31248873499b6d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/25_snapshot/include/snapshot/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SNAPSHOT_BAKE_CONFIG_H +#define SNAPSHOT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/25_snapshot/project.json b/fggl/ecs2/flecs/examples/c/25_snapshot/project.json new file mode 100644 index 0000000000000000000000000000000000000000..8dd3c44ad3b41650c20dd83dea1e25f6d87205ab --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/25_snapshot/project.json @@ -0,0 +1,10 @@ +{ + "id": "snapshot", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": ["flecs"] + } +} diff --git a/fggl/ecs2/flecs/examples/c/25_snapshot/src/main.c b/fggl/ecs2/flecs/examples/c/25_snapshot/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..a306625558bc9b42f34b8fd57f2fb4a288f2e67a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/25_snapshot/src/main.c @@ -0,0 +1,52 @@ +#include <snapshot.h> + +typedef struct { + double x, y; +} Position, Velocity; + +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + printf("Move {%f, %f}\n", p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + ecs_entity_t e = + ecs_set(world, 0, Position, {0, 0}); + ecs_set(world, e, Velocity, {1, 2}); + + /* Take a snapshot that records the current state of the entity */ + printf("Take snapshot\n"); + ecs_snapshot_t *s = ecs_snapshot_take(world); + + /* Progress the world a few times, updates position */ + ecs_progress(world, 0); + ecs_progress(world, 0); + ecs_progress(world, 0); + + /* Restore snapshot. The snapshot cannot be used anymore after restoring. + * If an application still wants to use this snapshot after restoring, a new + * snapshot can be taken. */ + printf("Restore snapshot\n"); + ecs_snapshot_restore(world, s); + + /* Progress the world again, note that the state has been restored */ + ecs_progress(world, 0); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/27_filter_iter/include/filter_iter.h b/fggl/ecs2/flecs/examples/c/27_filter_iter/include/filter_iter.h new file mode 100644 index 0000000000000000000000000000000000000000..0dce267ae9d1e744874b537a538a5b1336efd470 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/27_filter_iter/include/filter_iter.h @@ -0,0 +1,16 @@ +#ifndef FILTER_ITER_H +#define FILTER_ITER_H + +/* This generated file contains includes for project dependencies */ +#include "filter_iter/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/27_filter_iter/include/filter_iter/bake_config.h b/fggl/ecs2/flecs/examples/c/27_filter_iter/include/filter_iter/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..9f496344c9f392df34e4aa3b8bb0e0c10c513bda --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/27_filter_iter/include/filter_iter/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 FILTER_ITER_BAKE_CONFIG_H +#define FILTER_ITER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/27_filter_iter/project.json b/fggl/ecs2/flecs/examples/c/27_filter_iter/project.json new file mode 100644 index 0000000000000000000000000000000000000000..6ddb4324b1679000ad660619ff03ea72ed28e3fc --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/27_filter_iter/project.json @@ -0,0 +1,12 @@ +{ + "id": "filter_iter", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/27_filter_iter/src/main.c b/fggl/ecs2/flecs/examples/c/27_filter_iter/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..c63f78ecbdb0b4686fa195968026c47629ca03a7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/27_filter_iter/src/main.c @@ -0,0 +1,54 @@ +#include <filter_iter.h> + +typedef struct { + double x, y; +} Position, Velocity; + +typedef double Mass; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set_name(world, 0, "e1"); + ecs_set(world, e1, Position, {0, 0}); + ecs_set(world, e1, Velocity, {1, 1}); + + ecs_entity_t e2 = ecs_set_name(world, 0, "e2"); + ecs_set(world, e2, Position, {10, 20}); + ecs_set(world, e2, Velocity, {1, 1}); + + /* Create a filter for the Movable type, which includes Position and + * Velocity. Make sure to only match tables that include both Position and + * Velocity. */ + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + { .id = ecs_id(Position) }, + { .id = ecs_id(Velocity) } + } + }); + + /* Iteration of filter is the same as a query */ + ecs_iter_t it = ecs_filter_iter(world, &f); + while (ecs_filter_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + printf("moved %s to {%f, %f}\n", + ecs_get_name(world, it.entities[i]), p[i].x, p[i].y); + } + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/include/bulk_add_remove.h b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/include/bulk_add_remove.h new file mode 100644 index 0000000000000000000000000000000000000000..8bb8187219d0dd4dce4cf0444efd470ad4470d77 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/include/bulk_add_remove.h @@ -0,0 +1,16 @@ +#ifndef BULK_ADD_REMOVE_H +#define BULK_ADD_REMOVE_H + +/* This generated file contains includes for project dependencies */ +#include "bulk_add_remove/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/include/bulk_add_remove/bake_config.h b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/include/bulk_add_remove/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..347a033e470b1c749f06fd8d61e9ab25cb2ed6b6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/include/bulk_add_remove/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 BULK_ADD_REMOVE_BAKE_CONFIG_H +#define BULK_ADD_REMOVE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/project.json b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/project.json new file mode 100644 index 0000000000000000000000000000000000000000..219df87004a6d4f6099fb3819367c630c407ff99 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/project.json @@ -0,0 +1,12 @@ +{ + "id": "bulk_add_remove", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/src/main.c b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..2e3a1ab7a72d12a095243312e9adc5f8c990b252 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/32_bulk_add_remove/src/main.c @@ -0,0 +1,61 @@ +#include <bulk_add_remove.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +typedef struct { + double value; +} Mass; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register Position component */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Position_Mass, Position, Mass); + ECS_TYPE(world, Movable, Position, Velocity); + + /* Create 3 entities with Position */ + ecs_bulk_new(world, Position_Mass, 3); + + /* There will be 3 entities with Position, 0 with Velocity */ + printf("There are %d entities with Position\n", ecs_count(world, Position)); + printf("There are %d entities with Velocity\n", ecs_count(world, Velocity)); + + /* Bulk-add Velocity to all entities with Position */ + ecs_bulk_add_remove(world, Velocity, 0, &(ecs_filter_t) { + .include = ecs_type(Position) + }); + + printf("\n -- Bulk add Velocity -- \n\n"); + + /* There will be 3 entities with Position and Velocity */ + printf("There are %d entities with Position, Velocity\n", ecs_count(world, Movable)); + + /* Bulk-remove Position from all entities with Velocity */ + ecs_bulk_add_remove(world, 0, Position, &(ecs_filter_t) { + .include = ecs_type(Velocity) + }); + + printf("\n -- Bulk remove Position -- \n\n"); + + /* There will be 0 entities with Position, 3 with Velocity */ + printf("There are %d entities with Position\n", ecs_count(world, Position)); + printf("There are %d entities with Velocity\n", ecs_count(world, Velocity)); + printf("There are %d entities with Mass\n", ecs_count(world, Mass)); + + printf("\n -- Bulk remove Mass -- \n\n"); + + /* Remove Mass from all entities */ + ecs_bulk_add_remove(world, 0, Mass, NULL); + printf("There are %d entities with Mass\n", ecs_count(world, Mass)); + + /* Cleanup: will invoke OnRemove system */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/33_bulk_delete/include/bulk_delete.h b/fggl/ecs2/flecs/examples/c/33_bulk_delete/include/bulk_delete.h new file mode 100644 index 0000000000000000000000000000000000000000..5a00ffa957c29945b89752af9f8aa75b1c89273c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/33_bulk_delete/include/bulk_delete.h @@ -0,0 +1,16 @@ +#ifndef BULK_DELETE_H +#define BULK_DELETE_H + +/* This generated file contains includes for project dependencies */ +#include "bulk_delete/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/33_bulk_delete/include/bulk_delete/bake_config.h b/fggl/ecs2/flecs/examples/c/33_bulk_delete/include/bulk_delete/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..700f26eaa139002c8de480de0050b77082fae90a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/33_bulk_delete/include/bulk_delete/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 BULK_DELETE_BAKE_CONFIG_H +#define BULK_DELETE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/33_bulk_delete/project.json b/fggl/ecs2/flecs/examples/c/33_bulk_delete/project.json new file mode 100644 index 0000000000000000000000000000000000000000..35fbd84dec1290b214217673645d82c0cf265098 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/33_bulk_delete/project.json @@ -0,0 +1,12 @@ +{ + "id": "bulk_delete", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/33_bulk_delete/src/main.c b/fggl/ecs2/flecs/examples/c/33_bulk_delete/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..2bdcf8c02b5db78d76e7856c7c8e36fcc9de7e93 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/33_bulk_delete/src/main.c @@ -0,0 +1,40 @@ +#include <bulk_delete.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register Position component */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Movable, Position, Velocity); + + /* Create 3 entities with Position */ + ecs_bulk_new(world, Position, 3); + + /* Create 3 entities with Position, Velocity */ + ecs_bulk_new(world, Movable, 3); + + /* There will be 6 entities with Position, 3 with Velocity */ + printf("There are %d entities with Position\n", ecs_count(world, Position)); + printf("There are %d entities with Velocity\n", ecs_count(world, Velocity)); + + printf("\n -- Bulk delete all entities with Velocity -- \n\n"); + + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Velocity) + }); + + /* There will be 3 entities with Position, 0 with Velocity */ + printf("There are %d entities with Position\n", ecs_count(world, Position)); + printf("There are %d entities with Velocity\n", ecs_count(world, Velocity)); + + /* Cleanup: will invoke OnRemove system */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/34_persistent_query/include/persistent_query.h b/fggl/ecs2/flecs/examples/c/34_persistent_query/include/persistent_query.h new file mode 100644 index 0000000000000000000000000000000000000000..18a9325b6672c91b1996a5360f5945345425a951 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/34_persistent_query/include/persistent_query.h @@ -0,0 +1,16 @@ +#ifndef PERSISTENT_QUERY_H +#define PERSISTENT_QUERY_H + +/* This generated file contains includes for project dependencies */ +#include "persistent_query/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/34_persistent_query/include/persistent_query/bake_config.h b/fggl/ecs2/flecs/examples/c/34_persistent_query/include/persistent_query/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..590dc80784f8ae8cd7ed0a782d52a11183ceb555 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/34_persistent_query/include/persistent_query/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PERSISTENT_QUERY_BAKE_CONFIG_H +#define PERSISTENT_QUERY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/34_persistent_query/project.json b/fggl/ecs2/flecs/examples/c/34_persistent_query/project.json new file mode 100644 index 0000000000000000000000000000000000000000..c06ef89cafbe8350dfdbca28723bb9651b9a0760 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/34_persistent_query/project.json @@ -0,0 +1,12 @@ +{ + "id": "persistent_query", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/34_persistent_query/src/main.c b/fggl/ecs2/flecs/examples/c/34_persistent_query/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..14bb9790ec3d9f77bc071f173d3c151a4700467c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/34_persistent_query/src/main.c @@ -0,0 +1,56 @@ +#include <persistent_query.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create a query. Queries are 'persistent' meaning they are registered with + * the world and continuously matched with new entities (tables). Queries + * are the fastest way to iterate over entities, as a lot of processing is + * done when entities are matched, outside of the main loop. + * + * Queries are the mechanism used by systems, and as such both accept the + * same signature expressions, and have similar performance. */ + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + + /* Create a few entities that match the query */ + ecs_entity_t E1 = ecs_set_name(world, 0, "e1"); + ecs_set(world, E1, Position, {1, 2}); + ecs_set(world, E1, Velocity, {1, 1}); + + ecs_entity_t E2 = ecs_set_name(world, 0, "e2"); + ecs_set(world, E2, Position, {1, 2}); + ecs_set(world, E2, Velocity, {1, 1}); + + /* Don't add Velocity here, E3 will not match query */ + ecs_entity_t E3 = ecs_set_name(world, 0, "e3"); + ecs_set(world, E3, Position, {1, 2}); + + /* Iterate over entities matching the query */ + ecs_iter_t it = ecs_query_iter(q); + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(world, it.entities[i]), p[i].x, p[i].y); + } + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/35_empty_system_signature/include/empty_system_signature.h b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/include/empty_system_signature.h new file mode 100644 index 0000000000000000000000000000000000000000..ee1a31606d5c667b06859cf5a4800540ec3a5be2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/include/empty_system_signature.h @@ -0,0 +1,16 @@ +#ifndef EMPTY_SYSTEM_SIGNATURE_H +#define EMPTY_SYSTEM_SIGNATURE_H + +/* This generated file contains includes for project dependencies */ +#include "empty_system_signature/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/35_empty_system_signature/include/empty_system_signature/bake_config.h b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/include/empty_system_signature/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..5faa907960cd180f56ab31431adcc4747b63d1c8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/include/empty_system_signature/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 EMPTY_SYSTEM_SIGNATURE_BAKE_CONFIG_H +#define EMPTY_SYSTEM_SIGNATURE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/35_empty_system_signature/project.json b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/project.json new file mode 100644 index 0000000000000000000000000000000000000000..c420630c314a60297d24e1d07ea780f91ad05e32 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/project.json @@ -0,0 +1,11 @@ +{ + "id": "empty_system_signature", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/35_empty_system_signature/src/main.c b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ea8e14b747f41f70bff83f5dbe56838b3e019083 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/35_empty_system_signature/src/main.c @@ -0,0 +1,27 @@ +#include <empty_system_signature.h> + +void MyTask(ecs_iter_t *it) { + (void)it; // Unused + printf("MyTask invoked!\n"); +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Define a system with an empty signature. Systems that do not match with + * any entities are invoked once per frame */ + ECS_SYSTEM(world, MyTask, EcsOnUpdate, 0); + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + printf("Application empty_system_signature is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/36_periodic_system/include/periodic_system.h b/fggl/ecs2/flecs/examples/c/36_periodic_system/include/periodic_system.h new file mode 100644 index 0000000000000000000000000000000000000000..4793c25821ca1249661f7a057351452598cf716c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/36_periodic_system/include/periodic_system.h @@ -0,0 +1,16 @@ +#ifndef PERIODIC_SYSTEM_H +#define PERIODIC_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "periodic_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/36_periodic_system/include/periodic_system/bake_config.h b/fggl/ecs2/flecs/examples/c/36_periodic_system/include/periodic_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..fbfcc181aa5ab51d0e72c7da5b6801b47756afe8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/36_periodic_system/include/periodic_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PERIODIC_SYSTEM_BAKE_CONFIG_H +#define PERIODIC_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/36_periodic_system/project.json b/fggl/ecs2/flecs/examples/c/36_periodic_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f2be77c7046d2761599780954c70ab681a1ebb40 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/36_periodic_system/project.json @@ -0,0 +1,12 @@ +{ + "id": "periodic_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/36_periodic_system/src/main.c b/fggl/ecs2/flecs/examples/c/36_periodic_system/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..9f6dc8b4d5118f99b0e15ce8c5f8b00a76b0d19e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/36_periodic_system/src/main.c @@ -0,0 +1,65 @@ +#include <periodic_system.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} + +void PrintPosition(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + printf("%s position = {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Define a system called Move that is executed every frame, and subscribes + * for the 'Position' and 'Velocity' components */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + /* Create a system that prints the position of an entity once per second */ + ECS_SYSTEM(world, PrintPosition, EcsOnUpdate, Position); + ecs_set_interval(world, PrintPosition, 1.0); + + /* Create an entity with Position and Velocity. Creating an entity with the + * ECS_ENTITY macro is an easy way to create entities with multiple + * components. Additionally, entities created with this macro can be looked + * up by their name ('MyEntity'). */ + ECS_ENTITY(world, MyEntity, Position, Velocity); + + /* Initialize values for the entity */ + ecs_set(world, MyEntity, Position, {0, 0}); + ecs_set(world, MyEntity, Velocity, {1, 1}); + + /* Run at 60FPS */ + ecs_set_target_fps(world, 60); + + printf("Application move_system is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/37_delta_time/include/delta_time.h b/fggl/ecs2/flecs/examples/c/37_delta_time/include/delta_time.h new file mode 100644 index 0000000000000000000000000000000000000000..7abc5c609e385247bb82df21cfcd54ed5dd264f2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/37_delta_time/include/delta_time.h @@ -0,0 +1,16 @@ +#ifndef DELTA_TIME_H +#define DELTA_TIME_H + +/* This generated file contains includes for project dependencies */ +#include "delta_time/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/37_delta_time/include/delta_time/bake_config.h b/fggl/ecs2/flecs/examples/c/37_delta_time/include/delta_time/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..db5c45b6a8f7aee32b417ba90c181814201a0ddc --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/37_delta_time/include/delta_time/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DELTA_TIME_BAKE_CONFIG_H +#define DELTA_TIME_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/37_delta_time/project.json b/fggl/ecs2/flecs/examples/c/37_delta_time/project.json new file mode 100644 index 0000000000000000000000000000000000000000..3797705f47fd1946d05278665fd242c6985d5c63 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/37_delta_time/project.json @@ -0,0 +1,12 @@ +{ + "id": "delta_time", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/37_delta_time/src/main.c b/fggl/ecs2/flecs/examples/c/37_delta_time/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..2197be552b78f2e8d40244a2dd67f0a6bb206142 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/37_delta_time/src/main.c @@ -0,0 +1,54 @@ +#include <delta_time.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + /* Use delta_time to update the entity proportionally to the amount of + * time that has passed inbetween frames. */ + p[i].x += v[i].x * (double)it->delta_time; + p[i].y += v[i].y * (double)it->delta_time; + + printf("%s moved to {.x = %f, .y = %f}\n", + ecs_get_name(it->world, it->entities[i]), + p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + ECS_ENTITY(world, MyEntity, Position, Velocity); + + ecs_set(world, MyEntity, Position, {0, 0}); + ecs_set(world, MyEntity, Velocity, {1, 1}); + + /* Set target FPS for main loop to 60 frames per second. The specified FPS + * is not a guarantee, which is why applications should use delta_time to + * progress a simulation, which contains the actual time passed. */ + ecs_set_target_fps(world, 60); + + printf("Application delta_time is running, press CTRL-C to exit...\n"); + + /* Run systems. The 0 is the delta_time parameter that will be used for the + * frame. If the application provides 0, flecs will measure the delta_time + * automatically inbetween frames. + * + * For deterministic simulations, applications will want to pass in a fixed + * value that does not rely on the system clock. */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/38_delta_system_time/include/delta_system_time.h b/fggl/ecs2/flecs/examples/c/38_delta_system_time/include/delta_system_time.h new file mode 100644 index 0000000000000000000000000000000000000000..df4ff6072676ba7656a6040352d13e1be6b2ad38 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/38_delta_system_time/include/delta_system_time.h @@ -0,0 +1,16 @@ +#ifndef DELTA_SYSTEM_TIME_H +#define DELTA_SYSTEM_TIME_H + +/* This generated file contains includes for project dependencies */ +#include "delta_system_time/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/38_delta_system_time/include/delta_system_time/bake_config.h b/fggl/ecs2/flecs/examples/c/38_delta_system_time/include/delta_system_time/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..78b31a7882154ccf83cca2008fef5584bf708d0a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/38_delta_system_time/include/delta_system_time/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DELTA_SYSTEM_TIME_BAKE_CONFIG_H +#define DELTA_SYSTEM_TIME_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/38_delta_system_time/project.json b/fggl/ecs2/flecs/examples/c/38_delta_system_time/project.json new file mode 100644 index 0000000000000000000000000000000000000000..4d21152d9e8d36fa00251da1694bdc9081aca89c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/38_delta_system_time/project.json @@ -0,0 +1,12 @@ +{ + "id": "delta_system_time", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/38_delta_system_time/src/main.c b/fggl/ecs2/flecs/examples/c/38_delta_system_time/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ca13f72f50d256f4a32e0a224a3f2f8e20575e2e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/38_delta_system_time/src/main.c @@ -0,0 +1,46 @@ +#include <delta_system_time.h> + +void Period05(ecs_iter_t *it) { + printf("t = 0.5, elapsed time = %f\n", (double)it->delta_system_time); +} + +void Period10(ecs_iter_t *it) { + printf("t = 1.0, elapsed time = %f\n", (double)it->delta_system_time); +} + +void Period20(ecs_iter_t *it) { + printf("t = 2.0, elapsed time = %f\n", (double)it->delta_system_time); +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Create three systems with different periods, and print for each the + * delta_system_time. + * + * For systems with a period, the regular delta_time is not as useful as it + * provides the time passed since the last frame. The delta_system_time + * addresses this by keeping track of the time elapsed since the last time + * the system was invoked. + * + * This value should approximate the specified period, but may differ due to + * aliassing issues (the main loop still ticks at its own frequency) or due + * to variability introduced by operating system scheduling / system clock. + */ + + ECS_SYSTEM(world, Period05, EcsOnUpdate, 0); + ECS_SYSTEM(world, Period10, EcsOnUpdate, 0); + ECS_SYSTEM(world, Period20, EcsOnUpdate, 0); + + ecs_set_interval(world, Period05, 0.5); + ecs_set_interval(world, Period10, 1.0); + ecs_set_interval(world, Period20, 2.0); + + ecs_set_target_fps(world, 60); + + printf("Application delta_system_time is running, press CTRL-C to exit...\n"); + + while ( ecs_progress(world, 0)); + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/39_get_mutable/include/get_mutable.h b/fggl/ecs2/flecs/examples/c/39_get_mutable/include/get_mutable.h new file mode 100644 index 0000000000000000000000000000000000000000..06eed02d9fe60f090750c1c64598d217631009a9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/39_get_mutable/include/get_mutable.h @@ -0,0 +1,16 @@ +#ifndef GET_MUTABLE_H +#define GET_MUTABLE_H + +/* This generated file contains includes for project dependencies */ +#include "get_mutable/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/39_get_mutable/include/get_mutable/bake_config.h b/fggl/ecs2/flecs/examples/c/39_get_mutable/include/get_mutable/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..eee2876954f44e07675c18d00254d1b5fb4efb47 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/39_get_mutable/include/get_mutable/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 GET_MUTABLE_BAKE_CONFIG_H +#define GET_MUTABLE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/39_get_mutable/project.json b/fggl/ecs2/flecs/examples/c/39_get_mutable/project.json new file mode 100644 index 0000000000000000000000000000000000000000..a686a366bac348f45da56002a2655cf562813cde --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/39_get_mutable/project.json @@ -0,0 +1,11 @@ +{ + "id": "get_mutable", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/39_get_mutable/src/main.c b/fggl/ecs2/flecs/examples/c/39_get_mutable/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..1e5d986d845a96b2df5188b4e76acfe342d805ce --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/39_get_mutable/src/main.c @@ -0,0 +1,52 @@ +#include <get_mutable.h> + +typedef struct Counter { + int value; +} Counter; + +void PrintCounterAdd(ecs_iter_t *it) { + for (int i = 0; i < it->count; i ++) { + printf("%s: Counter added\n", ecs_get_name(it->world, it->entities[i])); + } +} + +void PrintCounterSet(ecs_iter_t *it) { + Counter *counter = ecs_term(it, Counter, 1); + + for (int i = 0; i < it->count; i ++) { + printf("%s: Counter set to %d\n", + ecs_get_name(it->world, it->entities[i]), + counter[i].value); + } +} + +void inc(ecs_world_t *world, ecs_entity_t e, ecs_entity_t ecs_id(Counter)) { + bool is_added = false; + + Counter *c = ecs_get_mut(world, e, Counter, &is_added); + if (is_added) { + c->value = 0; + } + + c->value ++; + + ecs_modified(world, e, Counter); +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Counter); + ECS_SYSTEM(world, PrintCounterAdd, EcsOnAdd, Counter); + ECS_SYSTEM(world, PrintCounterSet, EcsOnSet, Counter); + + ECS_ENTITY(world, MyEntity, 0); + + inc(world, MyEntity, ecs_id(Counter)); + inc(world, MyEntity, ecs_id(Counter)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/40_task/include/task.h b/fggl/ecs2/flecs/examples/c/40_task/include/task.h new file mode 100644 index 0000000000000000000000000000000000000000..9b5cfbc8f185f86f7f87952b49756afe99d9f9ca --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/40_task/include/task.h @@ -0,0 +1,16 @@ +#ifndef TASK_H +#define TASK_H + +/* This generated file contains includes for project dependencies */ +#include "task/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/40_task/include/task/bake_config.h b/fggl/ecs2/flecs/examples/c/40_task/include/task/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..073f544886b8fa876ec39045f286c07baa32ab54 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/40_task/include/task/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 TASK_BAKE_CONFIG_H +#define TASK_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/40_task/project.json b/fggl/ecs2/flecs/examples/c/40_task/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f631c7925aeb1ce95957eeef9d7a8b91dd51724d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/40_task/project.json @@ -0,0 +1,12 @@ +{ + "id": "task", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/40_task/src/main.c b/fggl/ecs2/flecs/examples/c/40_task/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..9c8e56b7ae0f2056fff8f4435b39ff0c403cdbb6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/40_task/src/main.c @@ -0,0 +1,50 @@ +#include <task.h> + +typedef struct TaskContext { + int value; +} TaskContext; + +void MyTask(ecs_iter_t *it) { + (void)it; // Unused + printf("Task executed every second\n"); +} + +void My2ndTask(ecs_iter_t *it) { + (void)it; // Unused + printf("Task executed every 2 seconds\n"); +} + +void My3rdTask(ecs_iter_t *it) { + TaskContext *ctx = ecs_term(it, TaskContext, 1); + + printf("Task with context: %d\n", ctx->value); +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + // Tasks are systems that are not matched with any entities. + ECS_COMPONENT(world, TaskContext); + + // Basic task + ECS_SYSTEM(world, MyTask, EcsOnUpdate, 0); + + // Task that is executed every 2 seconds + ECS_SYSTEM(world, My2ndTask, EcsOnUpdate, 0); + ecs_set_interval(world, My2ndTask, 2.0); + + // It is possible to add components to a task, just like regular systems + ECS_SYSTEM(world, My3rdTask, EcsOnUpdate, SYSTEM:TaskContext); + ecs_set(world, My3rdTask, TaskContext, {10}); + + /* Set target FPS for main loop */ + ecs_set_target_fps(world, 1); + + printf("Application task is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/41_sorting/.gitignore b/fggl/ecs2/flecs/examples/c/41_sorting/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fff6aadd6112352fa355dd19be6a8678b11dfdc0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/41_sorting/.gitignore @@ -0,0 +1,5 @@ +.bake_cache +.DS_Store +.vscode +gcov +bin diff --git a/fggl/ecs2/flecs/examples/c/41_sorting/include/sorting.h b/fggl/ecs2/flecs/examples/c/41_sorting/include/sorting.h new file mode 100644 index 0000000000000000000000000000000000000000..e509299d4b4b523c3bee0af16897a17afacf084e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/41_sorting/include/sorting.h @@ -0,0 +1,16 @@ +#ifndef SORTING_H +#define SORTING_H + +/* This generated file contains includes for project dependencies */ +#include "sorting/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/41_sorting/include/sorting/bake_config.h b/fggl/ecs2/flecs/examples/c/41_sorting/include/sorting/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..5b90027133592ea262a84de4696b59293fa3407c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/41_sorting/include/sorting/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SORTING_BAKE_CONFIG_H +#define SORTING_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/41_sorting/project.json b/fggl/ecs2/flecs/examples/c/41_sorting/project.json new file mode 100644 index 0000000000000000000000000000000000000000..ee6bf41f7f907196e74bdd9ea88edefe4bb600d3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/41_sorting/project.json @@ -0,0 +1,12 @@ +{ + "id": "sorting", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/41_sorting/src/main.c b/fggl/ecs2/flecs/examples/c/41_sorting/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..99f21eb2a6a0d8dd3a83de96c5dec9b168ec41c7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/41_sorting/src/main.c @@ -0,0 +1,91 @@ +#include <sorting.h> + +typedef struct { + double x, y; +} Position; + +/* Order by x member of Position */ +int compare_position( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)e1; + (void)e2; + const Position *p1 = ptr1; + const Position *p2 = ptr2; + return (p1->x > p2->x) - (p1->x < p2->x); +} + +/* Iterate iterator, printed values will be ordered */ +void print_iter(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + printf("{%f, %f}\n", p[i].x, p[i].y); + } +} + +/* Iterate query */ +void print_query(ecs_query_t *q) { + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + print_iter(&it); + } +} + +/* Iterate system */ +void PrintSystem(ecs_iter_t *it) { + print_iter(it); +} + +int main(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + /* Create entities, set Position in random order */ + ecs_entity_t e = ecs_set(world, 0, Position, {3, 0}); + ecs_set(world, 0, Position, {1, 0}); + ecs_set(world, 0, Position, {6, 0}); + ecs_set(world, 0, Position, {2, 0}); + ecs_set(world, 0, Position, {5, 0}); + ecs_set(world, 0, Position, {4, 0}); + + /* Create system. To enable sorting for a system, we need to access its + * underlying query object */ + ECS_SYSTEM(world, PrintSystem, 0, Position); + ecs_query_t *q_system = ecs_get_system_query(world, PrintSystem); + + /* We can now invoke the order_by operation on the query */ + ecs_query_order_by(world, q_system, ecs_id(Position), compare_position); + + /* Create a query for component Position */ + ecs_query_t *q = ecs_query_new(world, "Position"); + + /* Order by Position component */ + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + /* Iterate query, print values of Position */ + printf("-- First iteration\n"); + print_query(q); + + /* Change the value of one entity, invalidating the order */ + ecs_set(world, e, Position, {7, 0}); + + /* Iterate query again, printed values are still ordered */ + printf("\n-- Second iteration\n"); + print_query(q); + + /* Create new entity to show that data is also sorted for system */ + ecs_set(world, 0, Position, {3, 0}); + + /* Run the system, output will be sorted */ + printf("\n-- System iteration\n"); + ecs_run(world, PrintSystem, 0, NULL); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/42_pair/.gitignore b/fggl/ecs2/flecs/examples/c/42_pair/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fff6aadd6112352fa355dd19be6a8678b11dfdc0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/42_pair/.gitignore @@ -0,0 +1,5 @@ +.bake_cache +.DS_Store +.vscode +gcov +bin diff --git a/fggl/ecs2/flecs/examples/c/42_pair/include/pair.h b/fggl/ecs2/flecs/examples/c/42_pair/include/pair.h new file mode 100644 index 0000000000000000000000000000000000000000..7e9f34c592c15e6732872928814a6f04dcf282a9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/42_pair/include/pair.h @@ -0,0 +1,16 @@ +#ifndef PAIR_H +#define PAIR_H + +/* This generated file contains includes for project dependencies */ +#include "pair/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/42_pair/include/pair/bake_config.h b/fggl/ecs2/flecs/examples/c/42_pair/include/pair/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..6a8c2d68f7befb672958636faea6f718243d374c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/42_pair/include/pair/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PAIR_BAKE_CONFIG_H +#define PAIR_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/42_pair/project.json b/fggl/ecs2/flecs/examples/c/42_pair/project.json new file mode 100644 index 0000000000000000000000000000000000000000..db3b8983bf44f8c111e013293040f548fcd2976d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/42_pair/project.json @@ -0,0 +1,11 @@ +{ + "id": "pair", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/42_pair/src/main.c b/fggl/ecs2/flecs/examples/c/42_pair/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..d271883cf71819b1f5eb3729dc77115476afaaae --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/42_pair/src/main.c @@ -0,0 +1,91 @@ +#include <pair.h> + +/* Ordinary position & velocity components */ +typedef struct { + double x, y; +} Position, Velocity; + +/* This component will be used as a relation. A relation can be added to an + * entity together with a relation object, which is the thing to which relation + * applies. In this case, the ExpiryTime relation will apply to a component that + * we want to remove after a timeout. */ +typedef struct ExpiryTimer { + double expiry_time; + double t; +} ExpiryTimer; + +/* Create a system that matches all entities with an ExpiryTimer relation */ +void ExpireComponents(ecs_iter_t *it) { + /* First, get the pair data, just like a regular component */ + ExpiryTimer *et = ecs_term(it, ExpiryTimer, 1); + + /* Get the handle to the pair. This will tell us on which component the + * pair is applied */ + ecs_entity_t pair = ecs_term_id(it, 1); + + /* Get the id of the component (object) of the pair */ + ecs_entity_t comp = ecs_pair_object(it->world, pair); + + /* Iterate as usual */ + for (int32_t i = 0; i < it->count; i ++) { + /* Increase timer. When timer hits expiry time, remove component */ + et[i].t += (double)it->delta_time; + if (et[i].t >= et[i].expiry_time) { + /* Remove both the component and the pair. If the pair would not + * be removed, the system would still be invoked after this. */ + printf("Remove component '%s'\n", ecs_get_name(it->world, comp)); + + /* Removes component (Position or Velocity) */ + ecs_remove_id(it->world, it->entities[i], comp); + + /* Removes pair */ + ecs_remove_id(it->world, it->entities[i], pair); + } + } +} + +int main(void) { + ecs_world_t *world = ecs_init(); + + /* Register components and pair as a regular component */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, ExpiryTimer); + + /* Create a system that matches all entities with an ExpiryTimer relation */ + ECS_SYSTEM(world, ExpireComponents, EcsOnUpdate, (ExpiryTimer, *)); + + /* Create an entity with Position and Velocity */ + ecs_entity_t e = ecs_new(world, 0); + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + /* Assign pair with ExpiryTime and Position. After 3 seconds Position will + * be removed from the entity */ + ecs_set_pair(world, e, ExpiryTimer, ecs_id(Position), { + .expiry_time = 3 + }); + + /* Also assign pair with ExpiryTime and Velocity. After 2 seconds the + * Velocity component will be removed. */ + ecs_set_pair(world, e, ExpiryTimer, ecs_id(Velocity), { + .expiry_time = 2 + }); + + /* Note that ExpiryTime is added to the same entity twice, which happens + * because the pair objects are different */ + + /* Run the main loop until both components have been removed */ + ecs_set_target_fps(world, 10); + + printf("Running...\n"); + + while (ecs_progress(world, 0)) { + /* As soon as both components are removed, exit main loop */ + if (!ecs_get_type(world, e)) { + break; + } + } + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/44_switch/include/switch.h b/fggl/ecs2/flecs/examples/c/44_switch/include/switch.h new file mode 100644 index 0000000000000000000000000000000000000000..fc8750bbc2e2df28f5c37bc6ab3e6309fc82122d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/44_switch/include/switch.h @@ -0,0 +1,16 @@ +#ifndef SWITCH_H +#define SWITCH_H + +/* This generated file contains includes for project dependencies */ +#include "switch/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/44_switch/include/switch/bake_config.h b/fggl/ecs2/flecs/examples/c/44_switch/include/switch/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..02caf6be094dcfbeea9569e282c52ccb2437276d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/44_switch/include/switch/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SWITCH_BAKE_CONFIG_H +#define SWITCH_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/44_switch/project.json b/fggl/ecs2/flecs/examples/c/44_switch/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f6cb3b43bf2f982241fbbaa439ab89480cabecd2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/44_switch/project.json @@ -0,0 +1,12 @@ +{ + "id": "switch", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/44_switch/src/main.c b/fggl/ecs2/flecs/examples/c/44_switch/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..b3a381267c8c6c3f3d84b554bac63f1b7dd7451b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/44_switch/src/main.c @@ -0,0 +1,75 @@ +#include <switch.h> + +/* This example shows how to use switch and case roles, which let an application + * build efficient state machines. Without switch and case, an application would + * have to add a separate tag for each state, while ensuring that the previous + * state is removed. This is not only less usable, but also not very performant. + * + * With tags, different combinations of states can grow quickly, which can + * scatter entities across many different tables. With switches, entities with + * different states are still stored in the same table, which makes it much + * faster to change states, and improves iteration performance. + * + * Additionally, adding a case to an entity will remove the previous case, which + * makes the switch/case combination especially suited for state machihnes. + */ + +void Walk(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ecs_entity_t *m = ecs_term(it, ecs_entity_t, 1); + ecs_entity_t *d = ecs_term(it, ecs_entity_t, 2); + + for (int i = 0; i < it->count; i ++) { + printf("%s: Movement: %s, Direction: %s\n", + ecs_get_name(world, it->entities[i]), + ecs_get_name(world, m[i]), + ecs_get_name(world, d[i])); + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Create a Movement state machine with 3 states */ + ECS_TAG(world, Standing); + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Standing, Walking, Running); + + /* Create a Direction state machine with 4 states */ + ECS_TAG(world, Front); + ECS_TAG(world, Back); + ECS_TAG(world, Left); + ECS_TAG(world, Right); + ECS_TYPE(world, Direction, Front, Back, Left, Right); + + /* Create a system that subscribers for all entities that have a Direction + * and that are walking */ + ECS_SYSTEM(world, Walk, EcsOnUpdate, CASE | Walking, SWITCH | Direction); + + /* Create a few entities with various state combinations */ + ECS_ENTITY(world, e1, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + + ECS_ENTITY(world, e2, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Left); + + ECS_ENTITY(world, e3, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Right); + + /* Add Walking to e4. This will remove the Running case */ + ecs_add_id(world, e3, ECS_CASE | Walking); + + /* Set target FPS for main loop */ + ecs_set_target_fps(world, 1); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/45_subquery/include/subquery.h b/fggl/ecs2/flecs/examples/c/45_subquery/include/subquery.h new file mode 100644 index 0000000000000000000000000000000000000000..31aa820866cb99923f8717734fdc355abc44967d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/45_subquery/include/subquery.h @@ -0,0 +1,16 @@ +#ifndef SUBQUERY_H +#define SUBQUERY_H + +/* This generated file contains includes for project dependencies */ +#include "subquery/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/45_subquery/include/subquery/bake_config.h b/fggl/ecs2/flecs/examples/c/45_subquery/include/subquery/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a354954b7550655236ccf5feb5b34cb1d6e49fed --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/45_subquery/include/subquery/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SUBQUERY_BAKE_CONFIG_H +#define SUBQUERY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/45_subquery/project.json b/fggl/ecs2/flecs/examples/c/45_subquery/project.json new file mode 100644 index 0000000000000000000000000000000000000000..3d776dc2389280617da443807cb099b707053df4 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/45_subquery/project.json @@ -0,0 +1,11 @@ +{ + "id": "subquery", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/45_subquery/src/main.c b/fggl/ecs2/flecs/examples/c/45_subquery/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..1fcaf07bb8165d48dfe160928e5fbb2188a5276d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/45_subquery/src/main.c @@ -0,0 +1,49 @@ +#include <subquery.h> + +/* Subqueries allow applications to narrow down the potential set of tables to + * match with to a small size, which makes creation and deletion of queries + * efficient enough to do in the main loop. Additionally, subqueries have a + * lower adminstrative footprint than regular queries, which reduces the amount + * of memory that is required to store them. */ + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Register components */ + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create three entities with different sets of components */ + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Velocity); + + /* Create a parent query that subscribes for all entities with Position */ + ecs_query_t *q_parent = ecs_query_new(world, "Position"); + + /* Create a subquery that selects the subset of entities matched by q_parent + * that have the Velocity component. */ + ecs_query_t *q_sub = ecs_subquery_new(world, q_parent, "Velocity"); + + /* Iterate the subquery. Note that only e2 is matched, since it has both + * Position and Velocity */ + ecs_iter_t it = ecs_query_iter(q_sub); + while (ecs_query_next(&it)) { + for (int i = 0; i < it.count; i ++) { + printf("%s matched\n", ecs_get_name(world, it.entities[i])); + } + } + + /* The subquery could also have subscribed itself for both Position and + * Velocity, in which case it would still only have matched against the + * entities of the parent query, but the result would have been harder to + * see. */ + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/46_global_component/include/global_component.h b/fggl/ecs2/flecs/examples/c/46_global_component/include/global_component.h new file mode 100644 index 0000000000000000000000000000000000000000..a8420866afe6492156d4f32db144a7aabb9bd6e6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/46_global_component/include/global_component.h @@ -0,0 +1,20 @@ +#ifndef GLOBAL_COMPONENT_H +#define GLOBAL_COMPONENT_H + +/* This generated file contains includes for project dependencies */ +#include "global_component/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Declare external component handle when other source files need to access the + * component handle */ +ECS_COMPONENT_EXTERN(Position); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/46_global_component/include/global_component/bake_config.h b/fggl/ecs2/flecs/examples/c/46_global_component/include/global_component/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..7e3a001157cb2fde05993a1a78358e99cfe4261f --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/46_global_component/include/global_component/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 GLOBAL_COMPONENT_BAKE_CONFIG_H +#define GLOBAL_COMPONENT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/46_global_component/project.json b/fggl/ecs2/flecs/examples/c/46_global_component/project.json new file mode 100644 index 0000000000000000000000000000000000000000..097882aba17ae58d8b2f255fb8a29cab043344ba --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/46_global_component/project.json @@ -0,0 +1,12 @@ +{ + "id": "global_component", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/46_global_component/src/main.c b/fggl/ecs2/flecs/examples/c/46_global_component/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..e3571ddec258c9d91fa75e9e70824c59372b34bc --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/46_global_component/src/main.c @@ -0,0 +1,29 @@ +#include <global_component.h> + +typedef struct { + double x, y; +} Position; + +/* Declare component handle globally so other functions can access it. The + * header file contains an extern declaration for, so other source files + * can use the component id as well. */ +ECS_COMPONENT_DECLARE(Position); + +ecs_entity_t create_entity(ecs_world_t *world) { + /* Use component handle without passing it into the function */ + return ecs_new(world, Position); +} + +int main(void) { + ecs_world_t *world = ecs_init(); + + /* Assign component id to previously declared variables */ + ECS_COMPONENT_DEFINE(world, Position); + + ecs_entity_t e = create_entity(world); + + printf("Has Position: %s\n", + ecs_has(world, e, Position) ? "true" : "false"); + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/47_deferred_operations/.gitignore b/fggl/ecs2/flecs/examples/c/47_deferred_operations/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fff6aadd6112352fa355dd19be6a8678b11dfdc0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/47_deferred_operations/.gitignore @@ -0,0 +1,5 @@ +.bake_cache +.DS_Store +.vscode +gcov +bin diff --git a/fggl/ecs2/flecs/examples/c/47_deferred_operations/include/deferred_operations.h b/fggl/ecs2/flecs/examples/c/47_deferred_operations/include/deferred_operations.h new file mode 100644 index 0000000000000000000000000000000000000000..90279743a600699afec6a054ebfd249b3f4ab69e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/47_deferred_operations/include/deferred_operations.h @@ -0,0 +1,16 @@ +#ifndef DEFERRED_OPERATIONS_H +#define DEFERRED_OPERATIONS_H + +/* This generated file contains includes for project dependencies */ +#include "deferred_operations/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/47_deferred_operations/include/deferred_operations/bake_config.h b/fggl/ecs2/flecs/examples/c/47_deferred_operations/include/deferred_operations/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..7f292724e3f20a242ea62c1156c38e75517a6d40 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/47_deferred_operations/include/deferred_operations/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DEFERRED_OPERATIONS_BAKE_CONFIG_H +#define DEFERRED_OPERATIONS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/47_deferred_operations/project.json b/fggl/ecs2/flecs/examples/c/47_deferred_operations/project.json new file mode 100644 index 0000000000000000000000000000000000000000..bb071dcec2df7594e70cfd32af55fd80520735a7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/47_deferred_operations/project.json @@ -0,0 +1,12 @@ +{ + "id": "deferred_operations", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/47_deferred_operations/src/main.c b/fggl/ecs2/flecs/examples/c/47_deferred_operations/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..4ed4ea5ac1e3ac476a90b6ae6dc9179efe79c7ea --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/47_deferred_operations/src/main.c @@ -0,0 +1,46 @@ +#include <deferred_operations.h> + +typedef struct { + double x, y; +} Position, Velocity; + +void SetVelocity(ecs_iter_t *it) { + Velocity *v = ecs_term(it, Velocity, 1); + + for (int i = 0; i < it->count; i ++) { + printf("Velocity set to {%f, %f}\n", v[i].x, v[i].y); + } +} + +int main(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + /* Create OnSet system so we can see when Velocity is actually set */ + ECS_SYSTEM(world, SetVelocity, EcsOnSet, Velocity); + + /* Create 3 entities that have Position */ + ecs_bulk_new(world, Position, 3); + + /* Create a query for Position to set Velocity for each entity with Position. + * Because adding a component changes the underlying data structures, we + * need to defer the operations until we have finished iterating. */ + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + printf("Defer begin\n"); + ecs_defer_begin(world); + + while (ecs_query_next(&it)) { + for (int i = 0; i < it.count; i ++) { + ecs_set(world, it.entities[i], Velocity, {1, 2}); + } + } + + printf("Defer end\n"); + ecs_defer_end(world); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/48_is_alive/include/is_alive.h b/fggl/ecs2/flecs/examples/c/48_is_alive/include/is_alive.h new file mode 100644 index 0000000000000000000000000000000000000000..606a1a75d3c52280f29114710f865062b528c647 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/48_is_alive/include/is_alive.h @@ -0,0 +1,16 @@ +#ifndef IS_ALIVE_H +#define IS_ALIVE_H + +/* This generated file contains includes for project dependencies */ +#include "is_alive/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/48_is_alive/include/is_alive/bake_config.h b/fggl/ecs2/flecs/examples/c/48_is_alive/include/is_alive/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a53a3a1559e93b2ccd932112aefcd38c2787e9a2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/48_is_alive/include/is_alive/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 IS_ALIVE_BAKE_CONFIG_H +#define IS_ALIVE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/48_is_alive/project.json b/fggl/ecs2/flecs/examples/c/48_is_alive/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f5bf260b78a97368cb0f388120c1a583c3820759 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/48_is_alive/project.json @@ -0,0 +1,12 @@ +{ + "id": "is_alive", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/48_is_alive/src/main.c b/fggl/ecs2/flecs/examples/c/48_is_alive/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..37b283692cb3fe53a0e4334d5ef8becabdea18b3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/48_is_alive/src/main.c @@ -0,0 +1,22 @@ +#include <is_alive.h> + +int main(void) { + ecs_world_t *world = ecs_init(); + + /* Create new entity, will be alive */ + ecs_entity_t e1 = ecs_new(world, 0); + printf("e1 is alive: %d\n", ecs_is_alive(world, e1)); + + /* Entity will not be alive after deleting */ + ecs_delete(world, e1); + printf("e1 is alive: %d\n", ecs_is_alive(world, e1)); + + /* Create new entity, will return same id but new generation */ + ecs_entity_t e2 = ecs_new(world, 0); + + /* e2 is alive, but e1 is not because the generation increased */ + printf("e1 is alive: %d\n", ecs_is_alive(world, e1)); + printf("e2 is alive: %d\n", ecs_is_alive(world, e2)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/49_singleton/include/singleton.h b/fggl/ecs2/flecs/examples/c/49_singleton/include/singleton.h new file mode 100644 index 0000000000000000000000000000000000000000..9c7ac0a951dd7440ac92d24a24e3a0688bd877ab --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/49_singleton/include/singleton.h @@ -0,0 +1,16 @@ +#ifndef SINGLETON_H +#define SINGLETON_H + +/* This generated file contains includes for project dependencies */ +#include "singleton/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/49_singleton/include/singleton/bake_config.h b/fggl/ecs2/flecs/examples/c/49_singleton/include/singleton/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4d158e277e3873a305a732970d05d186e38c5246 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/49_singleton/include/singleton/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SINGLETON_BAKE_CONFIG_H +#define SINGLETON_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/49_singleton/project.json b/fggl/ecs2/flecs/examples/c/49_singleton/project.json new file mode 100644 index 0000000000000000000000000000000000000000..5be9ab3704cd998fe2bdaece50b79895108c3d2c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/49_singleton/project.json @@ -0,0 +1,12 @@ +{ + "id": "singleton", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/49_singleton/src/main.c b/fggl/ecs2/flecs/examples/c/49_singleton/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..d5e1dc8f65adfb44139c6c4620b945deb88424de --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/49_singleton/src/main.c @@ -0,0 +1,58 @@ +// Some compilers will flag $Game as an identifier with a $ symbol. This is not +// correct, as $Game is stringified by the ECS_SYSTEM macro. +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" +#endif + +#include <singleton.h> + +typedef struct Game { + int score; +} Game; + +typedef struct { + double x, y; +} Position; + +void KeepScore(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + // The singleton component can be retrieved as a regular column + Game *g = ecs_term(it, Game, 2); + + for (int i = 0; i < it->count; i ++) { + if (p[i].x > 1) { + // Use as pointer, since it's a single value + g->score ++; + } + } +} + +int main(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Game); + + // Systems can request a singleton component by prefixing the name with $ + ECS_SYSTEM(world, KeepScore, EcsOnUpdate, Position, $Game); + + // Singleton components can simply be set on the world + ecs_singleton_set(world, Game, {10}); + + // Similarly, they can be retrieved from the world as well + const Game *g = ecs_singleton_get(world, Game); + printf("Score: %d\n", g->score); + + // Create a few dummy entities + ecs_set(world, 0, Position, {0, 1}); + ecs_set(world, 0, Position, {1, 2}); + ecs_set(world, 0, Position, {2, 3}); + + // Run system + ecs_progress(world, 0); + + printf("Score: %d\n", g->score); + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/50_disabled_component/include/disable_component.h b/fggl/ecs2/flecs/examples/c/50_disabled_component/include/disable_component.h new file mode 100644 index 0000000000000000000000000000000000000000..c58de65171fe551cfc766ea05b270e74be1c4fe6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/50_disabled_component/include/disable_component.h @@ -0,0 +1,16 @@ +#ifndef DISABLE_COMPONENT_H +#define DISABLE_COMPONENT_H + +/* This generated file contains includes for project dependencies */ +#include "disable_component/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/50_disabled_component/include/disable_component/bake_config.h b/fggl/ecs2/flecs/examples/c/50_disabled_component/include/disable_component/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b73eb55911342a17192fd0156865a35a9f43bcb9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/50_disabled_component/include/disable_component/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DISABLE_COMPONENT_BAKE_CONFIG_H +#define DISABLE_COMPONENT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/50_disabled_component/project.json b/fggl/ecs2/flecs/examples/c/50_disabled_component/project.json new file mode 100644 index 0000000000000000000000000000000000000000..b7f3886b71623a00030f4dcc59c1ad5a42b84ffe --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/50_disabled_component/project.json @@ -0,0 +1,12 @@ +{ + "id": "disable_component", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "public": false + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/50_disabled_component/src/main.c b/fggl/ecs2/flecs/examples/c/50_disabled_component/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..91b67dd0f7b6998b5507bf0fb343ef575fda9121 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/50_disabled_component/src/main.c @@ -0,0 +1,64 @@ +#include <disable_component.h> + +typedef struct { + double x, y; +} Position; + +void PrintPosition(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + Position *p = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + printf("%s: {%f, %f}\n", + ecs_get_name(world, it->entities[i]), p[i].x, p[i].y); + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, PrintPosition, 0, Position); + + ECS_ENTITY(world, e1, 0); + ECS_ENTITY(world, e2, 0); + ECS_ENTITY(world, e3, 0); + + ecs_set(world, e1, Position, {10, 20}); + ecs_set(world, e2, Position, {30, 40}); + ecs_set(world, e3, Position, {50, 60}); + + printf("e1 enabled: %d\n", ecs_is_component_enabled(world, e1, Position)); + printf("e2 enabled: %d\n", ecs_is_component_enabled(world, e2, Position)); + printf("e3 enabled: %d\n", ecs_is_component_enabled(world, e3, Position)); + + printf("\n1st run: all components enabled\n"); + ecs_run(world, PrintPosition, 0, NULL); + + /* Enable component of entity 1, disable component of entity 2 */ + ecs_enable_component(world, e1, Position, true); + ecs_enable_component(world, e2, Position, false); + + /* e2 will now show up as disabled */ + printf("\n"); + printf("e1 enabled: %d\n", ecs_is_component_enabled(world, e1, Position)); + printf("e2 enabled: %d\n", ecs_is_component_enabled(world, e2, Position)); + printf("e3 enabled: %d\n", ecs_is_component_enabled(world, e3, Position)); + + printf("\n2nd run: e2 is disabled\n"); + ecs_run(world, PrintPosition, 0, NULL); + + /* Print types. Both e1 and e2 will have DISABLED|Position. This does not + * actually mean that both are disabled. Instead it means that both entities + * have a bitset that tracks whether the component is disabled. */ + char *type = NULL; + printf("\n"); + printf("e1 type: %s\n", type = ecs_type_str(world, ecs_get_type(world, e1))); free(type); + printf("e2 type: %s\n", type = ecs_type_str(world, ecs_get_type(world, e2))); free(type); + printf("e3 type: %s\n", type = ecs_type_str(world, ecs_get_type(world, e3))); free(type); + + ecs_fini(world); +} + diff --git a/fggl/ecs2/flecs/examples/c/51_direct_access/include/direct_access.h b/fggl/ecs2/flecs/examples/c/51_direct_access/include/direct_access.h new file mode 100644 index 0000000000000000000000000000000000000000..1b507ec3f7d1b90203f6ad333b35813b9960f6ed --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/51_direct_access/include/direct_access.h @@ -0,0 +1,16 @@ +#ifndef DIRECT_ACCESS_H +#define DIRECT_ACCESS_H + +/* This generated file contains includes for project dependencies */ +#include "direct_access/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/51_direct_access/include/direct_access/bake_config.h b/fggl/ecs2/flecs/examples/c/51_direct_access/include/direct_access/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..c3a28bb8cb8e86dc08e3a8361ac203cd50678e7a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/51_direct_access/include/direct_access/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DIRECT_ACCESS_BAKE_CONFIG_H +#define DIRECT_ACCESS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/51_direct_access/project.json b/fggl/ecs2/flecs/examples/c/51_direct_access/project.json new file mode 100644 index 0000000000000000000000000000000000000000..77c970eb52135bbf7abd3fe1b8aeac96a4c9f71f --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/51_direct_access/project.json @@ -0,0 +1,12 @@ +{ + "id": "direct_access", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/51_direct_access/src/main.c b/fggl/ecs2/flecs/examples/c/51_direct_access/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..a5a0814cf41d76714b7c0882585509a78579d082 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/51_direct_access/src/main.c @@ -0,0 +1,75 @@ +#include <direct_access.h> + +typedef struct { + double x, y; +} Position, Velocity; + +int main(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, PV, Position, Velocity); + + /* Use the direct access API to set the Position and Velocity components. + * When using regular ecs_set operations, each operation would have to + * retrieve the underlying storage context for the entity, which can add + * significant overhead, especially when initializing large numbers of + * components and entities. + * + * The direct access API enables an application to interact with the storage + * context directly which gets rids of the redundant lookups. */ + + /* Find the table for Position, Velocity components, and find the columns + * for the Position and Velocity components. */ + ecs_table_t *table = ecs_table_from_type(world, ecs_type(PV)); + int32_t pos_column = ecs_table_find_column(table, ecs_id(Position)); + int32_t vel_column = ecs_table_find_column(table, ecs_id(Velocity)); + + /* Create a new entity id */ + ecs_entity_t e1 = ecs_new_id(world); + + /* Find the record for the entity. A record stores in which table, and where + * in that table an entity is stored. Since the entity does not yet exist, + * this operation will create a record for the entity. */ + ecs_record_t *r1 = ecs_record_find(world, e1); + + /* Insert the entity into the table. This will update the record so that the + * storage now knows where to find the entity. */ + ecs_table_insert(world, table, e1, r1); + + /* Initialize Position and Velocity values for the entity by looking up the + * pointers for the entity in each respective column */ + Position *p_mut = ecs_record_get_column(r1, pos_column, sizeof(Position)); + p_mut->x = 10; + p_mut->y = 20; + + Velocity *v_mut = ecs_record_get_column(r1, vel_column, sizeof(Velocity)); + v_mut->x = 1; + v_mut->y = 1; + + /* Initialize a second entity for the same table. We can reuse the existing + * table pointer and column indices to reduces overhead */ + ecs_entity_t e2 = ecs_new_id(world); + ecs_record_t *r2 = ecs_record_find(world, e2); + ecs_table_insert(world, table, e2, r2); + + p_mut = ecs_record_get_column(r2, pos_column, sizeof(Position)); + p_mut->x = 20; + p_mut->y = 30; + + v_mut = ecs_record_get_column(r2, vel_column, sizeof(Velocity)); + v_mut->x = 2; + v_mut->y = 2; + + /* Print values to the console using regular functions */ + const Position *p = ecs_get(world, e1, Position); + const Velocity *v = ecs_get(world, e1, Velocity); + printf("e1: Position{%f, %f}, Velocity{%f, %f}\n", p->x, p->y, v->x, v->y); + + p = ecs_get(world, e2, Position); + v = ecs_get(world, e2, Velocity); + printf("e2: Position{%f, %f}, Velocity{%f, %f}\n", p->x, p->y, v->x, v->y); + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/52_merge_world/include/merge_world.h b/fggl/ecs2/flecs/examples/c/52_merge_world/include/merge_world.h new file mode 100644 index 0000000000000000000000000000000000000000..59d8c8170f09a29f86946f131ebdf504d385351b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/52_merge_world/include/merge_world.h @@ -0,0 +1,16 @@ +#ifndef MERGE_WORLD_H +#define MERGE_WORLD_H + +/* This generated file contains includes for project dependencies */ +#include "merge_world/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/52_merge_world/include/merge_world/bake_config.h b/fggl/ecs2/flecs/examples/c/52_merge_world/include/merge_world/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..92854238a3e520da7301f6eb52b6051fcc0b217d --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/52_merge_world/include/merge_world/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 MERGE_WORLD_BAKE_CONFIG_H +#define MERGE_WORLD_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/52_merge_world/project.json b/fggl/ecs2/flecs/examples/c/52_merge_world/project.json new file mode 100644 index 0000000000000000000000000000000000000000..823e7d467d16c66f5f1c75c492557998a40abc73 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/52_merge_world/project.json @@ -0,0 +1,12 @@ +{ + "id": "merge_world", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/52_merge_world/src/main.c b/fggl/ecs2/flecs/examples/c/52_merge_world/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..e2e4a3df61b495bb4f5ce841997a5083c58fb456 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/52_merge_world/src/main.c @@ -0,0 +1,147 @@ +#include <merge_world.h> + +typedef struct { + double x, y; +} Position, Velocity; + +typedef struct { + double value; +} Mass; + +ECS_COMPONENT_DECLARE(Position); +ECS_COMPONENT_DECLARE(Velocity); +ECS_COMPONENT_DECLARE(Mass); + +ecs_world_t* create_world(void) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + ECS_COMPONENT_DEFINE(world, Velocity); + ECS_COMPONENT_DEFINE(world, Mass); + + return world; +} + +int main(void) { + ecs_world_t *src = create_world(); + ecs_world_t *dst = create_world(); + + /* This example will demonstrate how to populate a world, and then copy data + * from a source world to a destination world using the direct access API. + * This allows applications to asynchronously load data into a temporary + * storage. With the data loaded in the right format, it can be cheaply + * moved to a destination world. While the API uses low-level primitives and + * as a result is somewhat verbose, it represents the fastest way of copying + * data between worlds, and offers a high degree of flexibility. + * + * It should be noted that this is a "safety off" API, as incorrectly + * updating the columns of a table can corrupt the store. Applications are + * encouraged to wrap the direct access API into higher level operations. + * + * On a high level, the application needs to make sure that after the + * operation, all destination table columns are of the same size, and that the + * entity records in the destination world point to the new table. The + * application needs to also ensure that the source world/table is left in a + * consistent state. + */ + + + /* -- Step 1: Populate source world with entities */ + ecs_entity_t e1 = ecs_set(src, 0, Position, {10, 20}); + ecs_set(src, e1, Velocity, {1, 1}); + + ecs_entity_t e2 = ecs_set(src, 0, Position, {20, 30}); + ecs_set(src, e2, Velocity, {2, 2}); + + + /* -- Step 2: Obtain the table from the source world */ + ecs_table_t *src_table = ecs_table_from_str(src, "Position, Velocity"); + int32_t src_pos_column = ecs_table_find_column(src_table, ecs_id(Position)); + int32_t src_vel_column = ecs_table_find_column(src_table, ecs_id(Velocity)); + + + /* -- Step 3: find or create the table in the destination world. + * Copy values to a table in the destination world. To demonstrate the + * flexibility of the API, the data will be moved to a table that does not + * have the same number of columns as the source table. */ + ecs_table_t *dst_table = ecs_table_from_str(dst, "Position, Velocity, Mass"); + int32_t dst_pos_column = ecs_table_find_column(dst_table, ecs_id(Position)); + int32_t dst_vel_column = ecs_table_find_column(dst_table, ecs_id(Velocity)); + int32_t dst_mass_column = ecs_table_find_column(dst_table, ecs_id(Mass)); + + + /* -- Step 4: In addition to data columns, a table also has a column with + * entity ids and cached pointers to records. We need to move the entity ids + * to the destination table, as well as making sure that the source table + * records no longer point to the table */ + ecs_vector_t *entities = ecs_table_get_entities(src_table); + ecs_vector_t *records = ecs_table_get_records(src_table); + + /* Clear src records so that they no longer point to the src table */ + ecs_records_clear(records); + + + /* -- Step 5: Although the dst table is guaranteed to be empty, an + * application should check if the table contains data already and free it + * to prevent memory leaks */ + ecs_table_delete_column(dst, dst_table, dst_pos_column, NULL); + ecs_table_delete_column(dst, dst_table, dst_vel_column, NULL); + + /* Also free entities & record vectors */ + ecs_vector_t *dst_entities = ecs_table_get_entities(dst_table); + ecs_vector_t *dst_records = ecs_table_get_records(dst_table); + if (dst_entities) ecs_vector_free(dst_entities); + if (dst_records) ecs_vector_free(dst_records); + + + /* -- Step 6: Assign entities & record vectors to destination world. Also + * make sure that the records are updated to point to the dst table. */ + ecs_records_update(dst, entities, records, dst_table); + ecs_table_set_entities(dst_table, entities, records); + ecs_table_set_entities(src_table, NULL, NULL); + + + /* -- Step 7: Set the columns from the source table to the dst table */ + ecs_vector_t *src_pos = ecs_table_get_column(src_table, src_pos_column); + ecs_table_set_column(dst, dst_table, dst_pos_column, src_pos); + ecs_table_set_column(src, src_table, src_pos_column, NULL); + + ecs_vector_t *src_vel = ecs_table_get_column(src_table, src_vel_column); + ecs_table_set_column(dst, dst_table, dst_vel_column, src_vel); + ecs_table_set_column(src, src_table, src_vel_column, NULL); + + + /* -- Step 8: We need to ensure that the Mass column has the correct number + * of elements. By providing NULL to the column parameter the function will + * automatically create (or resize) a vector that is of the correct size. */ + ecs_table_set_column(dst, dst_table, dst_mass_column, NULL); + + + /* Done! Query the destination table to verify entitities have been + * correctly transferred. */ + + ecs_query_t *q = ecs_query_new(dst, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count; i ++) { + printf("%u: Position{%f, %f}, Velocity{%f, %f}\n", + (uint32_t)it.entities[i], p[i].x, p[i].y, v[i].x, v[i].y); + } + } + + /* Ensure that the entities have the correct type. This relies on the + * records being correctly updated. */ + char *type_str = ecs_type_str(dst, ecs_get_type(dst, e1)); + printf("e1: [%s]\n", type_str); + ecs_os_free(type_str); + + type_str = ecs_type_str(dst, ecs_get_type(dst, e2)); + printf("e2: [%s]\n", type_str); + ecs_os_free(type_str); + + return ecs_fini(dst), ecs_fini(src); +} diff --git a/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/include/dynamic_buffer.h b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/include/dynamic_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..2db0b4fdf55be6d9631ace1d27b70d12c9616abd --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/include/dynamic_buffer.h @@ -0,0 +1,16 @@ +#ifndef DYNAMIC_BUFFER_H +#define DYNAMIC_BUFFER_H + +/* This generated file contains includes for project dependencies */ +#include "dynamic_buffer/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..2316e49c5f13c18327e7fb8f6b25b2869df099b7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/include/dynamic_buffer/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DYNAMIC_BUFFER_BAKE_CONFIG_H +#define DYNAMIC_BUFFER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/project.json b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/project.json new file mode 100644 index 0000000000000000000000000000000000000000..1926f04f4fb1cc745a93945bbfd2677e84780310 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/project.json @@ -0,0 +1,12 @@ +{ + "id": "dynamic_buffer", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/src/main.c b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..41e13f1aa1f637a2afbaf45bf54a972f8040f1d2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/53_dynamic_buffer/src/main.c @@ -0,0 +1,130 @@ +#include <dynamic_buffer.h> + +typedef struct { + double x, y; +} Position; + +/* Non-POD component type with a dynamic buffer */ +typedef struct { + int *data; + size_t count; +} DynamicBuffer; + +/* Lifecycle callbacks for the DynamicBuffer. These ensure that when a component + * is created, destructed, copied or moved no memory corruption or leakage + * happens. */ +ECS_CTOR(DynamicBuffer, ptr, { + printf("DynamicBuffer::ctor\n"); + ptr->data = NULL; + ptr->count = 0; +}) + +ECS_DTOR(DynamicBuffer, ptr, { + printf("DynamicBuffer::dtor\n"); + free(ptr->data); +}) + +ECS_COPY(DynamicBuffer, dst, src, { + printf("DynamicBuffer::copy\n"); + if (dst->data) { + free(dst->data); + } + + size_t size = sizeof(int) * src->count; + dst->data = malloc(size); + dst->count = src->count; + memcpy(dst->data, src->data, size); +}) + +ECS_MOVE(DynamicBuffer, dst, src, { + printf("DynamicBuffer::move\n"); + if (dst->data) { + free(dst->data); + } + + dst->data = src->data; + dst->count = src->count; + + src->data = NULL; + src->count = 0; +}) + +/* Forward declare component handles */ +ECS_COMPONENT_DECLARE(Position); +ECS_COMPONENT_DECLARE(DynamicBuffer); + +/* Add an element to a new or existing buffer */ +void add_elem(ecs_world_t *ecs, ecs_entity_t e, int value) { + DynamicBuffer *ptr = ecs_get_mut(ecs, e, DynamicBuffer, NULL); + + ptr->count ++; + ptr->data = realloc(ptr->data, ptr->count * sizeof(int)); + ptr->data[ptr->count - 1] = value; +} + +/* Remove element from buffer */ +void remove_elem(ecs_world_t *ecs, ecs_entity_t e, size_t elem) { + DynamicBuffer *ptr = ecs_get_mut(ecs, e, DynamicBuffer, NULL); + + size_t last = ptr->count - 1; + + if (last >= elem) { + if (last - elem) { + ptr->data[elem] = ptr->data[last]; + } + + ptr->count --; + } +} + +/* Get element from a buffer */ +int* get_elem(ecs_world_t *ecs, ecs_entity_t e, size_t elem) { + const DynamicBuffer *ptr = ecs_get(ecs, e, DynamicBuffer); + + if (ptr && (ptr->count > elem)) { + return &ptr->data[elem]; + } else { + return NULL; + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *ecs = ecs_init_w_args(argc, argv); + + /* Register ids for forward declared components */ + ECS_COMPONENT_DEFINE(ecs, Position); + ECS_COMPONENT_DEFINE(ecs, DynamicBuffer); + + /* Register lifecycle functions for comopnent */ + ecs_set_component_actions(ecs, DynamicBuffer, { + .ctor = ecs_ctor(DynamicBuffer), + .dtor = ecs_dtor(DynamicBuffer), + .copy = ecs_copy(DynamicBuffer), + .move = ecs_move(DynamicBuffer) + }); + + ecs_entity_t e = ecs_new_id(ecs); + + /* Add 3 elements to the buffer. The first add will add the DynamicBuffer + * element to the entity. */ + add_elem(ecs, e, 10); + add_elem(ecs, e, 20); + add_elem(ecs, e, 30); + + printf("Elem 1 = %d\n", *get_elem(ecs, e, 1)); + + /* Remove element. This will move the last element from the buffer to the + * removed element. */ + remove_elem(ecs, e, 1); + + printf("Elem 1 = %d (after remove)\n", *get_elem(ecs, e, 1)); + + /* Add component. This causes the entity to move between tables, and will + * invoke DynamicComponent::move to copy the component value from the src to + * the dst table. This also invokes DynamicComponent::ctor to construct the + * component in the dst table. */ + ecs_add(ecs, e, Position); + + /* This will invoke DynamicComponent::dtor. */ + return ecs_fini(ecs); +} diff --git a/fggl/ecs2/flecs/examples/c/54_simple_threads/include/simple_threads.h b/fggl/ecs2/flecs/examples/c/54_simple_threads/include/simple_threads.h new file mode 100644 index 0000000000000000000000000000000000000000..bd86351797970f469f5589aaa2a59ef5b36f9d8a --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/54_simple_threads/include/simple_threads.h @@ -0,0 +1,16 @@ +#ifndef SIMPLE_THREADS_H +#define SIMPLE_THREADS_H + +/* This generated file contains includes for project dependencies */ +#include "simple_threads/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/54_simple_threads/include/simple_threads/bake_config.h b/fggl/ecs2/flecs/examples/c/54_simple_threads/include/simple_threads/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..9abe0eef75fc55644fc1785aecb6d88bcf9e6f10 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/54_simple_threads/include/simple_threads/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_THREADS_BAKE_CONFIG_H +#define SIMPLE_THREADS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/54_simple_threads/project.json b/fggl/ecs2/flecs/examples/c/54_simple_threads/project.json new file mode 100644 index 0000000000000000000000000000000000000000..d5e7d2feda10347d91ad0f1ddf3993ba523c97f0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/54_simple_threads/project.json @@ -0,0 +1,12 @@ +{ + "id": "simple_threads", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/54_simple_threads/src/main.c b/fggl/ecs2/flecs/examples/c/54_simple_threads/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..26df5625f6c4cc8ddb42cb6c11dcf3d4455d3ce2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/54_simple_threads/src/main.c @@ -0,0 +1,59 @@ +#include <simple_threads.h> + +typedef struct { + double x, y; +} Position, Velocity; + +void Move(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + /* Print the id of the current stage. This allows us to see which + * entities are processed by which thread. */ + printf("Stage %d: %u\n", + ecs_get_stage_id(it->world), + (uint32_t)it->entities[i]); + } +} + +int main(int argc, char *argv[]) { + /* Flecs does not have an operating system abstraction for threading. To use + * threading, an application must first provide threading functions by + * setting the appropriate functions in the OS API. See the examples in the + * os_api folder for how this can be achieved. + * + * To run the example, add the posix OS API example as a dependency, and + * uncomment this line. Without doing this, the example will not run. */ + + // posix_set_os_api(); + + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + /* Create a bunch of entities */ + for (int i = 0; i < 10; i ++) { + ecs_entity_t e = ecs_set(world, 0, Position, {0, 0}); + ecs_set(world, e, Velocity, {1, 1}); + } + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + /* Set number of threads to 2. This will run all systems on all threads, and + * divide entities equally between the systems. */ + ecs_set_threads(world, 2); + + /* Run systems. */ + while (ecs_progress(world, 0)) { } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/55_custom_threads/include/custom_threads.h b/fggl/ecs2/flecs/examples/c/55_custom_threads/include/custom_threads.h new file mode 100644 index 0000000000000000000000000000000000000000..76798630e552c2121056d1136199106b3eda714e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/55_custom_threads/include/custom_threads.h @@ -0,0 +1,16 @@ +#ifndef CUSTOM_THREADS_H +#define CUSTOM_THREADS_H + +/* This generated file contains includes for project dependencies */ +#include "custom_threads/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/55_custom_threads/include/custom_threads/bake_config.h b/fggl/ecs2/flecs/examples/c/55_custom_threads/include/custom_threads/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..60c9a8600634f6a4ab75c1002fa2983394f78845 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/55_custom_threads/include/custom_threads/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CUSTOM_THREADS_BAKE_CONFIG_H +#define CUSTOM_THREADS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/55_custom_threads/project.json b/fggl/ecs2/flecs/examples/c/55_custom_threads/project.json new file mode 100644 index 0000000000000000000000000000000000000000..7d233d12a1d3d1c5091a3cdbddb2e48cc5cd25c8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/55_custom_threads/project.json @@ -0,0 +1,12 @@ +{ + "id": "custom_threads", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/55_custom_threads/src/main.c b/fggl/ecs2/flecs/examples/c/55_custom_threads/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..2b9fab530bcb45a726e4d7a855b3e88a8c19d057 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/55_custom_threads/src/main.c @@ -0,0 +1,197 @@ +#include <custom_threads.h> + +/* Component types */ +typedef struct { + double x, y; +} Position, Velocity; + +/* Type passed as thread arg */ +typedef struct { + ecs_world_t *stage; + ecs_entity_t system; + ecs_query_t *query; +} thread_ctx_t; + +/* Forward component declarations */ +ECS_COMPONENT_DECLARE(Position); +ECS_COMPONENT_DECLARE(Velocity); + +/* Implement a simple move system */ +void Move(ecs_iter_t *it) { + /* Get the two columns from the system signature */ + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + printf("Stage %d: %u\n", + ecs_get_stage_id(it->world), + (uint32_t)it->entities[i]); + } +} + +/* Dummy thread function */ +void* thread_func(void *arg) { + thread_ctx_t *ctx = arg; + ecs_world_t *world = ctx->stage; + + int32_t stage_id = ecs_get_stage_id(world); + int32_t stage_count = ecs_get_stage_count(world); + + /* The worker thread's main loop would start here, but because this is an + * example without threads and we call the thread explicitly from the main + * loop don't actually loop */ + + // while (!ecs_should_quit(world)) { + + /* This is where the thread would wait for the mainthread to signal that + * it is safe to start processing */ + // wait_for_staging_begin() + + /* Run system with the ecs_run_worker function. This will automatically + * subdivide entities evenly across threads. If the regular ecs_run + * function is used the system will evaluate all matching entities. */ + ecs_run_worker(world, ctx->system, stage_id, stage_count, 0, NULL); + + /* Evaluate query with the ecs_query_next_worker function. This will, + * just as with the system, automatically subdivide entities across + * threads. When the regular ecs_query_next function is used, all + * entities will be evaluated. */ + ecs_iter_t it = ecs_query_iter(ctx->query); + + while (ecs_query_next_worker(&it, stage_id, stage_count)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + for (int i = 0; i < it.count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + } + + /* So far, neither the system nor the query enqueued any operations. + * Enqueueing commands is done simply by calling the regular API with + * the provided staging context. The command will be executed when the + * stage is merged. */ + + /* The following operations will be enqueued */ + ecs_entity_t e = ecs_new(world, 0); + ecs_set(world, e, Position, {10, 20}); + + Velocity *v = ecs_get_mut(world, e, Velocity, NULL); + v->x = 1; + v->y = 2; + ecs_modified(world, e, Velocity); + + /* This is be where the thread would signal to the main thread that it + * is done processing. The main thread will wait for all threads to + * finish, after which the merge will take place. */ + // signal_main() + // } + + return NULL; +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT_DEFINE(world, Position); + ECS_COMPONENT_DEFINE(world, Velocity); + + /* Create both a system and a query so both can be demonstrated */ + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + + /* Create a bunch of entities */ + for (int i = 0; i < 5; i ++) { + ecs_entity_t e = ecs_set(world, 0, Position, {0, 0}); + ecs_set(world, e, Velocity, {1, 1}); + } + + /* Create two stages. This lets two threads concurrently mutate the world */ + ecs_set_stages(world, 2); + + /* Get the stage objects. These are passed to the threads and should be + * provided to API calls instead of the world. The stage objects provide a + * transparent mechanism for passing a thread context to regular flecs API + * functions without requiring a dedicated API for enqueuing commands. */ + ecs_world_t *stage_1 = ecs_get_stage(world, 0); + ecs_world_t *stage_2 = ecs_get_stage(world, 1); + + thread_ctx_t ctx_1 = { + .stage = stage_1, + .system = Move, + .query = q + }; + + thread_ctx_t ctx_2 = { + .stage = stage_2, + .system = Move, + .query = q + }; + + /* Start threads (replace this with an OS thread create function) */ + // thread_new(thread_func, ctx_1); + // thread_new(thread_func, ctx_2); + (void)ctx_1; (void)ctx_2; + + /* Set target FPS for main loop to 1 frame per second */ + ecs_set_target_fps(world, 1); + + /* Main loop. Instead of ecs_progress, use these functions to control when a + * frame starts/stops, and when merging should occur. */ + + while ( !ecs_should_quit(world)) { + printf("\nFrame begin\n"); + + /* Start frame. This does time measurements, and if an application has + * set a target FPS, this function will sleep for as long as necessary + * to ensure the application does not exceed the set FPS. The number + * passed to the function is delta_time, which should be set to the + * same value as what would ordinarly be passed to ecs_progress */ + ecs_frame_begin(world, 0); + + /* Start staging. After this call it is safe for threads to start adding + * commands to the queue. This also flags the moment at which it is no + * longer valid to do mutations on the regular world object. When an + * application would attempt to, for example, do + * "ecs_add(world, e, Position)" after staging_begin, an assert would be + * thrown. It is still safe to call functions that do not mutate the + * world, such as "ecs_has". + * + * If the world were mutated while in staged mode by accident (say, + * because asserts are not enabled) threads that are asynchronously + * accessing the world may run into undefined behavior as a result of + * data races. + */ + ecs_staging_begin(world); + + /* This is where the main thread would signal the threads to start + * processing (and enqueueing commands) */ + // signal_threads(); + + /* Stub running the threads */ + thread_func(&ctx_1); + thread_func(&ctx_2); + + /* This is where the main thread would wait for the threads to finish + * processing */ + // wait_for_threads(); + + /* End staging. This will automatically merge data from all threads. + * Applications will be able to disable automatic merging, and even take + * control over which stages are merged and which stages aren't. It is + * valid to begin/end staging multiple times per frame. This is commonly + * required when an application has multiple sync points. */ + ecs_staging_end(world); + + /* Finish frame. This executes post-frame actions */ + ecs_frame_end(world); + } + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/56_custom_merge/include/custom_merge.h b/fggl/ecs2/flecs/examples/c/56_custom_merge/include/custom_merge.h new file mode 100644 index 0000000000000000000000000000000000000000..785581880ca2221221f5db96cc43b927ed2c0c8e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/56_custom_merge/include/custom_merge.h @@ -0,0 +1,16 @@ +#ifndef CUSTOM_MERGE_H +#define CUSTOM_MERGE_H + +/* This generated file contains includes for project dependencies */ +#include "custom_merge/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/56_custom_merge/include/custom_merge/bake_config.h b/fggl/ecs2/flecs/examples/c/56_custom_merge/include/custom_merge/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..dc8438c7fec7921958dd1ca128a868862008bb01 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/56_custom_merge/include/custom_merge/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CUSTOM_MERGE_BAKE_CONFIG_H +#define CUSTOM_MERGE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/56_custom_merge/project.json b/fggl/ecs2/flecs/examples/c/56_custom_merge/project.json new file mode 100644 index 0000000000000000000000000000000000000000..7d6f92a6995e0812ed15306964562d99f927aa68 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/56_custom_merge/project.json @@ -0,0 +1,12 @@ +{ + "id": "custom_merge", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/56_custom_merge/src/main.c b/fggl/ecs2/flecs/examples/c/56_custom_merge/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ab956f5445c9bd1c643795c7d9cf9f975e5a4772 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/56_custom_merge/src/main.c @@ -0,0 +1,56 @@ +#include <custom_merge.h> + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Create three stages. */ + ecs_set_stages(world, 3); + + /* By default stages are merged automatically when calling staging_end(). + * This behavior can be overridden so that an application can control when + * each stage is merged, and in what order. + * + * The set_automerge() function enables or disables automatic merging for + * all current stages in the world, and sets the default for new stages. */ + ecs_set_automerge(world, false); + + ecs_world_t *stage_1 = ecs_get_stage(world, 0); + ecs_world_t *stage_2 = ecs_get_stage(world, 1); + ecs_world_t *stage_3 = ecs_get_stage(world, 2); + + /* Enable automerging just for stage 1 */ + ecs_set_automerge(stage_1, true); + + /* Begin staging. Note that this also demonstrates a very minimal example of + * staging, without frame_begin() and frame_end() functions. Also note that + * we need to enable staging before we may write to a stage. */ + ecs_staging_begin(world); + + /* Create an entity in each stage */ + ecs_entity_t e1 = ecs_set_name(stage_1, 0, "e_stage_1"); + ecs_entity_t e2 = ecs_set_name(stage_2, 0, "e_stage_2"); + ecs_entity_t e3 = ecs_set_name(stage_3, 0, "e_stage_3"); + + /* End staging. This will only merge the stages for which automerging has + * been enabled, which in this case is just stage_1. */ + ecs_staging_end(world); + + /* Show that only e1 has a name */ + printf("e1: \"%s\", e2: %s, e3: %s\n", + ecs_get_name(world, e1), + ecs_get_name(world, e2), + ecs_get_name(world, e3)); + + /* Now merge the remaining stages. We could either call merge() on the world + * which merges all stages, or we could merge each individual stage. The + * latter gives us more control over the order in which stages are merged. + * Note that merging is only allowed when staging is disabled. */ + ecs_merge(stage_3); + ecs_merge(stage_2); + + /* All entities are now merged */ + printf("e1: \"%s\", e2: \"%s\", e3: \"%s\"\n", + ecs_get_name(world, e1), + ecs_get_name(world, e2), + ecs_get_name(world, e3)); +} diff --git a/fggl/ecs2/flecs/examples/c/60_observer/include/observer.h b/fggl/ecs2/flecs/examples/c/60_observer/include/observer.h new file mode 100644 index 0000000000000000000000000000000000000000..0a29e4b82340f26843f880cb29c00f8b8b0ef43e --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/60_observer/include/observer.h @@ -0,0 +1,16 @@ +#ifndef OBSERVER_H +#define OBSERVER_H + +/* This generated file contains includes for project dependencies */ +#include "observer/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/60_observer/include/observer/bake_config.h b/fggl/ecs2/flecs/examples/c/60_observer/include/observer/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b1d7efa313d2e0c4cc0e2ad21bd65e6a37ee3266 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/60_observer/include/observer/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OBSERVER_BAKE_CONFIG_H +#define OBSERVER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/60_observer/project.json b/fggl/ecs2/flecs/examples/c/60_observer/project.json new file mode 100644 index 0000000000000000000000000000000000000000..51247a481f26b3b3d79885f5a2abdd936c023e4c --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/60_observer/project.json @@ -0,0 +1,11 @@ +{ + "id": "observer", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/60_observer/src/main.c b/fggl/ecs2/flecs/examples/c/60_observer/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..cfe6edf87b3345272548341c185ff4f9f0ed6b3f --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/60_observer/src/main.c @@ -0,0 +1,44 @@ +#include <observer.h> + +typedef struct { + float x, y; +} Position, Velocity; + +void observer_callback(ecs_iter_t *it) { + for (int i = 0; i < it->count; i ++) { + printf("observer triggered!\n"); + } +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t Likes = ecs_new_id(world); + ecs_entity_t Apples = ecs_new_id(world); + + /* An observer triggers when an event matching one of its terms matches, and + * the entity matches with all other terms. */ + ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = { + {.id = ecs_id(Position)}, + {.id = ecs_id(Velocity)}, + {.id = ecs_pair(Likes, EcsWildcard)} + }, + .events = {EcsOnAdd}, + .callback = observer_callback + }); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add(world, e, Position); /* Observer does not trigger */ + + ecs_add(world, e, Velocity); /* Observer does not trigger */ + + /* Observer triggers, entity matches all of its terms */ + ecs_add_pair(world, e, Likes, Apples); + + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/c/61_custom_pipeline/include/custom_pipeline.h b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/include/custom_pipeline.h new file mode 100644 index 0000000000000000000000000000000000000000..ea8b94d4d0d8f83289f186d7fff26a1437ddaa5b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/include/custom_pipeline.h @@ -0,0 +1,16 @@ +#ifndef CUSTOM_PIPELINE_H +#define CUSTOM_PIPELINE_H + +/* This generated file contains includes for project dependencies */ +#include "custom_pipeline/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/61_custom_pipeline/include/custom_pipeline/bake_config.h b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/include/custom_pipeline/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..8b3d68a78b08b70712e3036ab7738fd1e4cca89b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/include/custom_pipeline/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CUSTOM_PIPELINE_BAKE_CONFIG_H +#define CUSTOM_PIPELINE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/c/61_custom_pipeline/project.json b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/project.json new file mode 100644 index 0000000000000000000000000000000000000000..294dfdad5548faeb8c106a0bcba4725f9a2f7b6b --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/project.json @@ -0,0 +1,12 @@ +{ + "id": "custom_pipeline", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ] + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/c/61_custom_pipeline/src/main.c b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..563c885b80b0e3198f8de7dd34fbdc3d1a7e5156 --- /dev/null +++ b/fggl/ecs2/flecs/examples/c/61_custom_pipeline/src/main.c @@ -0,0 +1,46 @@ +#include <custom_pipeline.h> + +void PreFrameSystem(ecs_iter_t *it) { + (void)it; /* Silence unused parameter warning */ + printf("- PreFrame\n"); +} + +void OnFrameSystem(ecs_iter_t *it) { + (void)it; + printf("- OnFrame\n"); +} + +void PostFrameSystem(ecs_iter_t *it) { + (void)it; + printf("- PostFrame\n"); +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Create three custom phases for the pipeline */ + ECS_TAG(world, PreFrame); + ECS_TAG(world, OnFrame); + ECS_TAG(world, PostFrame); + + /* Create the pipeline with the three custom phases */ + ECS_PIPELINE(world, MyPipeline, PreFrame, OnFrame, PostFrame); + + /* Make the world use the custom pipeline. After this the pipeline should + * no longer be modified. */ + ecs_set_pipeline(world, MyPipeline); + + /* Define a system for each of the phases. Define them in opposite order so + * we can see the pipeline orders them correctly. */ + ECS_SYSTEM(world, PreFrameSystem, PreFrame, 0); + ECS_SYSTEM(world, OnFrameSystem, OnFrame, 0); + ECS_SYSTEM(world, PostFrameSystem, PostFrame, 0); + + /* Run the pipeline once */ + printf("Begin\n"); + ecs_progress(world, 0); + printf("End\n"); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/examples/cpp/01_helloworld/include/helloworld.h b/fggl/ecs2/flecs/examples/cpp/01_helloworld/include/helloworld.h new file mode 100644 index 0000000000000000000000000000000000000000..aa7484570800beb9dfa91071e7be9c78526c0de7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/01_helloworld/include/helloworld.h @@ -0,0 +1,16 @@ +#ifndef HELLOWORLD_H +#define HELLOWORLD_H + +/* This generated file contains includes for project dependencies */ +#include "helloworld/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/01_helloworld/include/helloworld/bake_config.h b/fggl/ecs2/flecs/examples/cpp/01_helloworld/include/helloworld/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d59119a3974e2d11106e8f5203d92118974ae194 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/01_helloworld/include/helloworld/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 HELLOWORLD_BAKE_CONFIG_H +#define HELLOWORLD_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/01_helloworld/project.json b/fggl/ecs2/flecs/examples/cpp/01_helloworld/project.json new file mode 100644 index 0000000000000000000000000000000000000000..63abe120b3344602b7ee6bd26514d9cdd93d4f76 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/01_helloworld/project.json @@ -0,0 +1,13 @@ +{ + "id": "helloworld", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/01_helloworld/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/01_helloworld/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ceaeec10dfa7110034491ec43a6e34bfcedead87 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/01_helloworld/src/main.cpp @@ -0,0 +1,42 @@ +#include <helloworld.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + // Create the world, pass arguments for overriding the number of threads,fps + // or for starting the admin dashboard (see flecs.h for details). + flecs::world ecs(argc, argv); + + // Create a new empty entity. Entity names are optional. + auto e = ecs.entity("MyEntity"); + + // Set the Position component on the entity. + e.set<Position>({5, 10}); + + // Print the Position component + const Position *ptr = e.get<Position>(); + std::cout << "Position: " + << ptr->x << ", " + << ptr->y << std::endl << std::endl; + + // Set both Position and Velocity + e.set([](Position& p, Velocity& v) { + p = {10, 20}; + v = {1, 2}; + }); + + // Print both Position and Velocity + e.get([](const Position& p, const Velocity& v) { + std::cout << "Position: " << p.x << ", " << p.y << std::endl; + std::cout << "Velocity: " << v.x << ", " << v.y << std::endl; + }); + + return 0; +} diff --git a/fggl/ecs2/flecs/examples/cpp/02_simple_system/include/simple_system.h b/fggl/ecs2/flecs/examples/cpp/02_simple_system/include/simple_system.h new file mode 100644 index 0000000000000000000000000000000000000000..ec6497bfe58e8674a7fa232b4933a0111550b754 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/02_simple_system/include/simple_system.h @@ -0,0 +1,16 @@ +#ifndef SIMPLE_SYSTEM_H +#define SIMPLE_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "simple_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/02_simple_system/include/simple_system/bake_config.h b/fggl/ecs2/flecs/examples/cpp/02_simple_system/include/simple_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..044ec0f59d8f0d9f8df62a3748632f6ac2f36a34 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/02_simple_system/include/simple_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_SYSTEM_BAKE_CONFIG_H +#define SIMPLE_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/02_simple_system/project.json b/fggl/ecs2/flecs/examples/cpp/02_simple_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..d5a5370583e360588b4f363e8d97f1bf1cda8042 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/02_simple_system/project.json @@ -0,0 +1,13 @@ +{ + "id": "simple_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/02_simple_system/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/02_simple_system/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5dbac10c7b408033d0fc1a369cb418d0ee035f6d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/02_simple_system/src/main.cpp @@ -0,0 +1,27 @@ +#include <simple_system.h> +#include <iostream> + +/* Component type */ +struct Message { + const char *text; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.system<Message>() + .each([](Message& messages) { + std::cout << messages.text << std::endl; + }); + + ecs.entity().set<Message>({"Hello Flecs!"}); + + ecs.set_target_fps(1); + + std::cout << "Application simple_system is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/03_move_system/include/move_system.h b/fggl/ecs2/flecs/examples/cpp/03_move_system/include/move_system.h new file mode 100644 index 0000000000000000000000000000000000000000..ca50325b19f3797633ddbc17aa311e752db2a705 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/03_move_system/include/move_system.h @@ -0,0 +1,16 @@ +#ifndef MOVE_SYSTEM_H +#define MOVE_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "move_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/03_move_system/include/move_system/bake_config.h b/fggl/ecs2/flecs/examples/cpp/03_move_system/include/move_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..dcac16e9bbc3f581144dc57d11a5eea3a42d41c0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/03_move_system/include/move_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 MOVE_SYSTEM_BAKE_CONFIG_H +#define MOVE_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/03_move_system/project.json b/fggl/ecs2/flecs/examples/cpp/03_move_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..4b82e401abea067c470af10c7a8506c24a937bed --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/03_move_system/project.json @@ -0,0 +1,13 @@ +{ + "id": "move_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/03_move_system/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/03_move_system/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad49cf727eb132fb5a6e09a29ed916c33b44d990 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/03_move_system/src/main.cpp @@ -0,0 +1,37 @@ +#include <move_system.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + + std::cout << "Moved " << e.name() << " to {" << + p.x << ", " << p.y << "}" << std::endl; + }); + + ecs.entity("MyEntity") + .set<Position>({0, 0}) + .set<Velocity>({1, 1}); + + ecs.set_target_fps(1); + + std::cout << "Application move_system is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/04_simple_module/include/simple_module.h b/fggl/ecs2/flecs/examples/cpp/04_simple_module/include/simple_module.h new file mode 100644 index 0000000000000000000000000000000000000000..1a884df8a8e845e6e60aa4e0f4b2ca17b951114f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/04_simple_module/include/simple_module.h @@ -0,0 +1,41 @@ +#ifndef SIMPLE_MODULE_H +#define SIMPLE_MODULE_H + +/* This generated file contains includes for project dependencies */ +#include "simple_module/bake_config.h" +#include <iostream> + +struct simple_module { + // Define types inside module scope. This is not mandatory, but ensures + // that their fully qualified Flecs name matches the C++ type name. It also + // ensures that type names cannot clash between modules. + + struct Position { + double x, y; + }; + + struct Velocity { + double x, y; + }; + + simple_module(flecs::world& ecs) { + /* Register module with world */ + ecs.module<simple_module>(); + + /* Register components */ + ecs.component<Position>(); + ecs.component<Velocity>(); + + /* Register system */ + ecs.system<Position, const Velocity>("Move") + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + + std::cout << "Moved " << e.name() << " to {" << + p.x << ", " << p.y << "}" << std::endl; + }); + } +}; + +#endif diff --git a/fggl/ecs2/flecs/examples/cpp/04_simple_module/include/simple_module/bake_config.h b/fggl/ecs2/flecs/examples/cpp/04_simple_module/include/simple_module/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..bf3bba2128467f18b3d77af9c21c6957b73f0719 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/04_simple_module/include/simple_module/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_MODULE_BAKE_CONFIG_H +#define SIMPLE_MODULE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/04_simple_module/project.json b/fggl/ecs2/flecs/examples/cpp/04_simple_module/project.json new file mode 100644 index 0000000000000000000000000000000000000000..fdb8a8e7c828f7b78ff9ac792a36da71c3f04ea9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/04_simple_module/project.json @@ -0,0 +1,13 @@ +{ + "id": "simple_module", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/04_simple_module/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/04_simple_module/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eba9bf8bd1b335d29eb3c527b22b7bdef55858ba --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/04_simple_module/src/main.cpp @@ -0,0 +1,20 @@ +#include <simple_module.h> + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Import module containing Position, Velocity and Move */ + ecs.import<simple_module>(); + + /* Create entity with imported components */ + ecs.entity("MyEntity") + .set<simple_module::Position>({10, 20}) + .set<simple_module::Velocity>({1, 1}); + + ecs.set_target_fps(1); + + /* Run imported move system */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/include/add_remove_system.h b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/include/add_remove_system.h new file mode 100644 index 0000000000000000000000000000000000000000..b36ed4aef6eb912cf7c716b68e1fb2234db28136 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/include/add_remove_system.h @@ -0,0 +1,16 @@ +#ifndef ADD_REMOVE_SYSTEM_H +#define ADD_REMOVE_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "add_remove_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/include/add_remove_system/bake_config.h b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/include/add_remove_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..18fe6388198dd033e25254f1bb6eb88852972c0d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/include/add_remove_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 ADD_REMOVE_SYSTEM_BAKE_CONFIG_H +#define ADD_REMOVE_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/project.json b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..cf97d422be3344aeba510909e7fdcfae71c6cb4e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/project.json @@ -0,0 +1,13 @@ +{ + "id": "add_remove_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e36fd82e1471e6715fb19ac2914ee95524c35829 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/05_add_remove_system/src/main.cpp @@ -0,0 +1,43 @@ +#include <add_remove_system.h> +#include <iostream> + +struct Position { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* This system will be called when Position is added */ + ecs.system<Position>() + .kind(flecs::OnAdd) + .each([](Position& p) { + p.x = 10; + p.y = 20; + std::cout << "OnAdd" << std::endl; + }); + + /* This system will be called when Position is removed */ + ecs.system<Position>() + .kind(flecs::OnRemove) + .each([](Position& p) { + std::cout << "OnRemove {" << p.x << ", " << p.y << "}" << std::endl; + }); + + auto e = ecs.entity(); + + /* Add Position. This will trigger the OnAdd system */ + e.add<Position>(); + + /* Remove Position. This will trigger the OnRemove system. */ + e.remove<Position>(); + + /* Add Position again. This will retrigger the OnAdd system */ + e.add<Position>(); + + /* Add Position again. This does not retrigger the OnAdd system since the + * entity already has Position */ + e.add<Position>(); +} diff --git a/fggl/ecs2/flecs/examples/cpp/06_set_system/include/set_system.h b/fggl/ecs2/flecs/examples/cpp/06_set_system/include/set_system.h new file mode 100644 index 0000000000000000000000000000000000000000..5c380f1200eab3d96f1d635e9ae75fd1a19ebabf --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/06_set_system/include/set_system.h @@ -0,0 +1,16 @@ +#ifndef SET_SYSTEM_H +#define SET_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "set_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/06_set_system/include/set_system/bake_config.h b/fggl/ecs2/flecs/examples/cpp/06_set_system/include/set_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..412b0e457c59df88c818771f8fd6d73c2f012876 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/06_set_system/include/set_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SET_SYSTEM_BAKE_CONFIG_H +#define SET_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/06_set_system/project.json b/fggl/ecs2/flecs/examples/cpp/06_set_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..b70ff6adbc5dac369c165f5206647daf1946a23e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/06_set_system/project.json @@ -0,0 +1,13 @@ +{ + "id": "set_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/06_set_system/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/06_set_system/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2bb03e18f41b1ac2c132312ab3416bdc5e1d33ec --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/06_set_system/src/main.cpp @@ -0,0 +1,30 @@ +#include <set_system.h> +#include <iostream> + +struct Position { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* This system will be called when Position is set */ + ecs.system<Position>() + .kind(flecs::OnSet) + .each([](Position& p) { + std::cout << "OnSet {" << p.x << ", " << p.y << "}" << std::endl; + }); + + auto e = ecs.entity(); + + /* Add Position. This will not trigger the OnSet system */ + e.add<Position>(); + + /* Set Position. This will trigger the OnSet system. */ + e.set<Position>({30, 40}); + + /* Set Position again. This will retrigger the OnSet system */ + e.set<Position>({50, 60}); +} diff --git a/fggl/ecs2/flecs/examples/cpp/08_hierarchy/include/hierarchy.h b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/include/hierarchy.h new file mode 100644 index 0000000000000000000000000000000000000000..8fff2d37ce875818d2c3567baed138d55bf86292 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/include/hierarchy.h @@ -0,0 +1,16 @@ +#ifndef HIERARCHY_H +#define HIERARCHY_H + +/* This generated file contains includes for project dependencies */ +#include "hierarchy/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/08_hierarchy/include/hierarchy/bake_config.h b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/include/hierarchy/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..6b3d117ad9c85daa1b828f13a8b8e61219548758 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/include/hierarchy/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 HIERARCHY_BAKE_CONFIG_H +#define HIERARCHY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/08_hierarchy/project.json b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/project.json new file mode 100644 index 0000000000000000000000000000000000000000..34facdc4483d3225a453c82ee00da0bf4c711522 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/project.json @@ -0,0 +1,13 @@ +{ + "id": "hierarchy", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/08_hierarchy/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8526e1dd3c7d9e645dbae1dfc4c3727bbaf5b004 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/08_hierarchy/src/main.cpp @@ -0,0 +1,126 @@ +#include <hierarchy.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct WorldPosition { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +/* Implement a simple move system */ +void Move(flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + + std::cout << e.name() << " moved to {.x = " + << p.x << ", .y = " + << p.y << "}" << std::endl; +} + +/* Implement a system that transforms world coordinates hierarchically. If the + * CASCADE column is set, it points to the world coordinate of the parent. This + * will be used to then transform Position to WorldPosition of the child. + * If the CASCADE column is not set, the system matched a root. In that case, + * just assign the Position to the WorldPosition. */ +void Transform(flecs::iter& it, WorldPosition* wp, Position *p) { + auto parent_wp = it.term<const WorldPosition>(3); + + if (!parent_wp.is_set()) { + for (auto row : it) { + wp[row].x = p[row].x; + wp[row].y = p[row].y; + + std::cout << it.entity(row).name() << " transformed to {.x = " + << wp[row].x << ", .y = " + << wp[row].y << "} <<root>>" << std::endl; + } + } else { + for (auto row : it) { + /* Note that we're not using row to access parent_wp. This function + * ('Transform') is invoked for every matching archetype, and the + * parent is part of the archetype. That means that all entities + * that are being iterated over have the same, single parent. */ + wp[row].x = parent_wp->x + p[row].x; + wp[row].y = parent_wp->y + p[row].y; + + std::cout << it.entity(row).name() << " transformed to {.x = " + << wp[row].x << ", .y = " + << wp[row].y << "} <<child>>" << std::endl; + } + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Move entities with Position and Velocity */ + ecs.system<Position, const Velocity>().each(Move); + + /* Transform local coordinates to world coordinates. A CASCADE column + * guarantees that entities are evaluated breadth-first, according to the + * hierarchy. This system will depth-sort based on parents that have the + * WorldPosition component. + * + * Note that columns that have modifiers (like CASCADE) cannot be provided + * as a template parameter, and have to be specified using hte 'signature' + * method. This string will be appended to the signature, so that the full + * signature for this system will be: + * WorldPosition, Position. CASCADE:WorldPosition. + * + * Additionally, note that we're using 'action' instead of 'each'. Actions + * provide more flexibility and performance, but also are slightly more + * complex to implement. + * + * In this case we need to use 'action', since otherwise we cannot access + * the WorldPosition component from the parent. + */ + ecs.system<WorldPosition, Position>() + .term<WorldPosition>() + .superset(flecs::ChildOf, flecs::Cascade) + .oper(flecs::Optional) + .iter(Transform); + + // Create entity hierarchy + ecs.entity("Root") + .add<WorldPosition>() + .set<Position>({0, 0}) + .set<Velocity>({1, 2}) + .scope([&]{ // From here entities are created with (ChildOf, Root) + ecs.entity("Child1") + .add<WorldPosition>() + .set<Position>({100, 100}) + .scope([&]{ // (ChildOf, Root::Child1) + ecs.entity("GChild1") + .add<WorldPosition>() + .set<Position>({1000, 1000}); + }); + + ecs.entity("Child2") + .add<WorldPosition>() + .set<Position>({200, 200}) + .scope([&]{ // (ChildOf, Root::Child2) + ecs.entity("GChild2") + .add<WorldPosition>() + .set<Position>({2000, 2000}); + }); + }); + + ecs.set_target_fps(1); + + std::cout + << "Application move_system is running, press CTRL-C to exit..." + << std::endl; + + /* Run systems */ + while ( ecs.progress()) { + std::cout << "----" << std::endl; + } +} diff --git a/fggl/ecs2/flecs/examples/cpp/10_inheritance/include/inheritance.h b/fggl/ecs2/flecs/examples/cpp/10_inheritance/include/inheritance.h new file mode 100644 index 0000000000000000000000000000000000000000..562a166e90c6eae2ba1e7565cb0a164f35ebb763 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/10_inheritance/include/inheritance.h @@ -0,0 +1,16 @@ +#ifndef INHERITANCE_H +#define INHERITANCE_H + +/* This generated file contains includes for project dependencies */ +#include "inheritance/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/10_inheritance/include/inheritance/bake_config.h b/fggl/ecs2/flecs/examples/cpp/10_inheritance/include/inheritance/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4523aa7a6f35bb9aa37e25155af91acfe340b2d5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/10_inheritance/include/inheritance/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 INHERITANCE_BAKE_CONFIG_H +#define INHERITANCE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/10_inheritance/project.json b/fggl/ecs2/flecs/examples/cpp/10_inheritance/project.json new file mode 100644 index 0000000000000000000000000000000000000000..c400e820a3c5e3f705398c089d723b97f0dabd3b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/10_inheritance/project.json @@ -0,0 +1,13 @@ +{ + "id": "inheritance", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/10_inheritance/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/10_inheritance/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fa8504a4b49a38dfbbdc7c830143c8b473f9ffcb --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/10_inheritance/src/main.cpp @@ -0,0 +1,95 @@ +#include <inheritance.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Force { + double x, y; +}; + +struct Mass { + double value; +}; + +/* Implement a move system with support for shared columns */ +void Move(const flecs::iter& it, Position *p, Force *f) { + auto m = it.column<const Mass>(3); + + for (auto row : it) { + /* Explicitly check if the Mass column is shared or not. If the column + * is shared, each entity that is currently iterated over shared the + * same base, and thus the same Mass value. This means that rather than + * accessing m as an array, we should access it as a single value. */ + if (!it.is_owned(3)) { + /* Mass is shared, use as single value */ + p[row].x += f[row].x / m->value; + p[row].y += f[row].y / m->value; + } else { + /* Mass is not shared, use as array */ + p[row].x += f[row].x / m[row].value; + p[row].y += f[row].y / m[row].value; + } + + /* Print something to the console so we can see the system is being + * invoked */ + std::cout << it.entity(row).name() << " moved to {.x = " + << p[row].x << ", .y = " + << p[row].y << "}" << std::endl; + } +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Define a system called Move that is executed every frame, and subscribes + * for the 'Position', 'Force' and 'Mass' components. Match the Mass + * component both on the entity itself and its supersets. */ + ecs.system<Position, Force>("Move") + .term<Mass>().set(flecs::Self | flecs::SuperSet) + .iter(Move); + + /* Demonstrate that a system can also use 'each' to abstract away from the + * difference between shared and owned components */ + ecs.system<Mass>().each( + [](flecs::entity e, Mass& m) { + std::cout << e.name() << ": Mass = " << m.value << std::endl; + }); + + /* Create two base entities */ + auto LightEntity = ecs.entity("LightEntity").set<Mass>({100}); + auto HeavyEntity = ecs.entity("HeavyEntity").set<Mass>({200}); + + /* Create an entity which does not share Mass from a base */ + ecs.entity("MyEntity") + .set<Position>({0, 0}) + .set<Force>({10, 10}) + .set<Mass>({50}); + + /* Create entities which share the Mass component from a base */ + ecs.entity("MyInstance1") + .is_a(LightEntity) + .set<Position>({0, 0}) + .set<Force>({10, 10}); + + ecs.entity("MyInstance2") + .is_a(HeavyEntity) + .set<Position>({0, 0}) + .set<Force>({10, 10}); + + ecs.entity("MyInstance3") + .is_a(HeavyEntity) + .set<Position>({0, 0}) + .set<Force>({10, 10}); + + ecs.set_target_fps(1); + + std::cout << "Application inheritance is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/12_override/include/override.h b/fggl/ecs2/flecs/examples/cpp/12_override/include/override.h new file mode 100644 index 0000000000000000000000000000000000000000..4260c403b333ed25b106933c658acde396fa2c5b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/12_override/include/override.h @@ -0,0 +1,16 @@ +#ifndef OVERRIDE_H +#define OVERRIDE_H + +/* This generated file contains includes for project dependencies */ +#include "override/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/12_override/include/override/bake_config.h b/fggl/ecs2/flecs/examples/cpp/12_override/include/override/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b96f6d316537bcaee797522fa4de58dbdd3f69d1 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/12_override/include/override/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OVERRIDE_BAKE_CONFIG_H +#define OVERRIDE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/12_override/project.json b/fggl/ecs2/flecs/examples/cpp/12_override/project.json new file mode 100644 index 0000000000000000000000000000000000000000..928c07295748425a4c5c120d3f0d472b82f53788 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/12_override/project.json @@ -0,0 +1,13 @@ +{ + "id": "override", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/12_override/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/12_override/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e6897bc3f92df564c06a86d1f72d89f495f8cce --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/12_override/src/main.cpp @@ -0,0 +1,48 @@ +#include <override.h> +#include <iostream> + +struct Mass { + double value; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create base entity */ + auto base = ecs.entity().set<Mass>({10}); + + /* Create instances which share the Mass component from a base */ + auto instance = ecs.entity().is_a(base); + + /* Print value before overriding Mass. The component is not owned, as it is + * shared with the base entity. */ + std::cout << "Before overriding:" << std::endl; + std::cout << "instance: " + << instance.get<Mass>()->value << " (owned = " + << instance.owns<Mass>() << ")" << std::endl; + + /* Override Mass of instance, which will give instance a private copy of the + * Mass component. */ + instance.set<Mass>({20}); + + /* Print values after overriding Mass. The value of Mass for instance_1 will + * be the value assigned in the override (20). Instance now owns Mass, + * confirming it has a private copy of the component. */ + std::cout << "After overriding:" << std::endl; + std::cout << "instance: " + << instance.get<Mass>()->value << " (owned = " + << instance.owns<Mass>() << ")" << std::endl; + + /* Remove override of Mass. This will remove the private copy of the Mass + * component from instance. */ + instance.remove<Mass>(); + + /* Print value after removing the override Mass. The component is no longer + * owned, as the instance is again sharing the component with base. */ + std::cout << "After removing override:" << std::endl; + std::cout << "instance: " + << instance.get<Mass>()->value << " (owned = " + << instance.owns<Mass>() << ")" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/13_override_init/include/override_init.h b/fggl/ecs2/flecs/examples/cpp/13_override_init/include/override_init.h new file mode 100644 index 0000000000000000000000000000000000000000..ce64a1f727c6cdac48a5d61b334b901c12c9f18c --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/13_override_init/include/override_init.h @@ -0,0 +1,16 @@ +#ifndef OVERRIDE_INIT_H +#define OVERRIDE_INIT_H + +/* This generated file contains includes for project dependencies */ +#include "override_init/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/13_override_init/include/override_init/bake_config.h b/fggl/ecs2/flecs/examples/cpp/13_override_init/include/override_init/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..aeceda3d8e7a8637cf34dd2d163ab116761b9a64 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/13_override_init/include/override_init/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OVERRIDE_INIT_BAKE_CONFIG_H +#define OVERRIDE_INIT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/13_override_init/project.json b/fggl/ecs2/flecs/examples/cpp/13_override_init/project.json new file mode 100644 index 0000000000000000000000000000000000000000..103157a703191a190c8d2d81083d4f4d5c6e56f9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/13_override_init/project.json @@ -0,0 +1,13 @@ +{ + "id": "override_init", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/13_override_init/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/13_override_init/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0e5ab20c8c7fce43cd7650604821b56ab66b8370 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/13_override_init/src/main.cpp @@ -0,0 +1,29 @@ +#include <override_init.h> +#include <iostream> + +struct Mass { + double value; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create base entity */ + auto base = ecs.entity().set<Mass>({10}); + + /* Create instances which share the Mass component from a base */ + auto instance = ecs.entity().is_a(base); + + /* Add component without setting it. This will initialize the new component + * with the value from the base, which is a common approach to initializing + * components with a value when they are added. */ + instance.add<Mass>(); + + /* Print value of mass. The value will be equal to base, and the instance + * will own the component. */ + std::cout << "instance: " + << instance.get<Mass>()->value << " (owned = " + << instance.owns<Mass>() << ")" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/14_add_type/include/add_type.h b/fggl/ecs2/flecs/examples/cpp/14_add_type/include/add_type.h new file mode 100644 index 0000000000000000000000000000000000000000..774f07b40fe7b35272c2a8dfb74fa4928b976b24 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/14_add_type/include/add_type.h @@ -0,0 +1,16 @@ +#ifndef ADD_TYPE_H +#define ADD_TYPE_H + +/* This generated file contains includes for project dependencies */ +#include "add_type/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/14_add_type/include/add_type/bake_config.h b/fggl/ecs2/flecs/examples/cpp/14_add_type/include/add_type/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..1e59b177b88c1d10271d7ee55b44fea95cb6b229 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/14_add_type/include/add_type/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 ADD_TYPE_BAKE_CONFIG_H +#define ADD_TYPE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/14_add_type/project.json b/fggl/ecs2/flecs/examples/cpp/14_add_type/project.json new file mode 100644 index 0000000000000000000000000000000000000000..0a0649715b143414aceb097d1f31eef18283c0fd --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/14_add_type/project.json @@ -0,0 +1,13 @@ +{ + "id": "add_type", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/14_add_type/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/14_add_type/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d0f7870fb96312bac6921c7af9e7d4ea06ca154e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/14_add_type/src/main.cpp @@ -0,0 +1,34 @@ +#include <add_type.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + auto Movable = ecs.type("Movable") + .add<Position>() + .add<Velocity>(); + + auto e = ecs.entity() + .add(Movable); + + /* Test if entity has the components */ + std::cout << "After adding the type:" << std::endl; + std::cout << "Has Position? " << e.has<Position>() << std::endl; + std::cout << "Has Velocity? " << e.has<Velocity>() << std::endl; + + e.remove(Movable); + + std::cout << "After removing the type:" << std::endl; + std::cout << "Has Position? " << e.has<Position>() << std::endl; + std::cout << "Has Velocity? " << e.has<Velocity>() << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/15_auto_override/include/auto_override.h b/fggl/ecs2/flecs/examples/cpp/15_auto_override/include/auto_override.h new file mode 100644 index 0000000000000000000000000000000000000000..9884a68966ecc5675dd35f7e784be832fea61589 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/15_auto_override/include/auto_override.h @@ -0,0 +1,16 @@ +#ifndef AUTO_OVERRIDE_H +#define AUTO_OVERRIDE_H + +/* This generated file contains includes for project dependencies */ +#include "auto_override/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/15_auto_override/include/auto_override/bake_config.h b/fggl/ecs2/flecs/examples/cpp/15_auto_override/include/auto_override/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..e9beb630d3456be544934687c097d78794300641 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/15_auto_override/include/auto_override/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 AUTO_OVERRIDE_BAKE_CONFIG_H +#define AUTO_OVERRIDE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/15_auto_override/project.json b/fggl/ecs2/flecs/examples/cpp/15_auto_override/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f84590b2ce4922c005af1209fb6b05a5d41f07db --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/15_auto_override/project.json @@ -0,0 +1,13 @@ +{ + "id": "auto_override", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/15_auto_override/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/15_auto_override/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7dbf528cc37dd94a92c9c912f415bc19fb007bc2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/15_auto_override/src/main.cpp @@ -0,0 +1,45 @@ +#include <auto_override.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create entity with default values for Position and Velocity. Add the + * EcsDisabled tag to ensure the entity will not be matched by systems, + * since it is only used to provide initial component values. */ + auto Base = ecs.entity("Base") + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .add(flecs::Disabled); + + /* This type inherits from Base, as well as adding Position and Velocity as + * private components. This will automatically override the components as an + * entity with this type is created, which will initialize the private + * values with the values of the Base entity. This is a common approach to + * creating entities with an initialized set of components. */ + auto Movable = ecs.type("Movable") + .is_a(Base) + .add<Position>() + .add<Velocity>(); + + auto e = ecs.entity() + .add(Movable); + + const Position *p = e.get<Position>(); + const Velocity *v = e.get<Velocity>(); + + std::cout << "Position: {" << p->x << ", " << p->y << "} (owned = " + << e.owns<Position>() << "} " << std::endl; + std::cout << "Velocity: {" << v->x << ", " << v->y << "} (owned = " + << e.owns<Velocity>() << "} " << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/16_prefab/include/prefab.h b/fggl/ecs2/flecs/examples/cpp/16_prefab/include/prefab.h new file mode 100644 index 0000000000000000000000000000000000000000..31c61ff328348ab526778aa7b75d7a6e56861b2b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/16_prefab/include/prefab.h @@ -0,0 +1,16 @@ +#ifndef PREFAB_H +#define PREFAB_H + +/* This generated file contains includes for project dependencies */ +#include "prefab/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/16_prefab/include/prefab/bake_config.h b/fggl/ecs2/flecs/examples/cpp/16_prefab/include/prefab/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d091f5b8c16de8e9228d540333e1056e55252a24 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/16_prefab/include/prefab/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PREFAB_BAKE_CONFIG_H +#define PREFAB_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/16_prefab/project.json b/fggl/ecs2/flecs/examples/cpp/16_prefab/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f250c6640d8e30f99ce113b1ca8ef986ab2cb69d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/16_prefab/project.json @@ -0,0 +1,13 @@ +{ + "id": "prefab", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/16_prefab/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/16_prefab/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aac40f7bbfb6f388181a6e97bfd3d0d6814f5431 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/16_prefab/src/main.cpp @@ -0,0 +1,46 @@ +#include <prefab.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create a prefab. Prefabs are entities that are solely intended as + * templates for other entities. Prefabs are by default not matched with + * systems. In that way they are similar to regular entities with the + * EcsDisbled tag, except that they have more features which are + * demonstrated in the nested_prefab example. */ + auto BasePrefab = ecs.prefab("BasePrefab") + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + /* This type inherits from Base, as well as adding Position and Velocity as + * private components. This will automatically override the components as an + * entity with this type is created, which will initialize the private + * values with the values of the Base entity. This is a common approach to + * creating entities with an initialized set of components. */ + auto Base = ecs.type("Base") + .is_a(BasePrefab) + .add<Position>() + .add<Velocity>(); + + auto e = ecs.entity() + .add(Base); + + const Position *p = e.get<Position>(); + const Velocity *v = e.get<Velocity>(); + + std::cout << "Position: {" << p->x << ", " << p->y << "} (owned = " + << e.owns<Position>() << "} " << std::endl; + std::cout << "Velocity: {" << v->x << ", " << v->y << "} (owned = " + << e.owns<Velocity>() << "} " << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/include/prefab_variant.h b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/include/prefab_variant.h new file mode 100644 index 0000000000000000000000000000000000000000..44af11855d080c750ec7335e535b242be51b6d87 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/include/prefab_variant.h @@ -0,0 +1,16 @@ +#ifndef PREFAB_VARIANT_H +#define PREFAB_VARIANT_H + +/* This generated file contains includes for project dependencies */ +#include "prefab_variant/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/include/prefab_variant/bake_config.h b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/include/prefab_variant/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d69de54b7bd1dc2a8f461766f811ba95f1fe1e1c --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/include/prefab_variant/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PREFAB_VARIANT_BAKE_CONFIG_H +#define PREFAB_VARIANT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/project.json b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/project.json new file mode 100644 index 0000000000000000000000000000000000000000..cc465b0ddc7440f070c83dbb1bc731ac61d71174 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/project.json @@ -0,0 +1,13 @@ +{ + "id": "prefab_variant", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9c378f4913b9cad97316a752c769b29c17e87dd --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/17_prefab_variant/src/main.cpp @@ -0,0 +1,58 @@ +#include <prefab_variant.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create a prefab. Prefabs are entities that are solely intended as + * templates for other entities. Prefabs are by default not matched with + * systems. In that way they are similar to regular entities with the + * EcsDisbled tag, except that they have more features which are + * demonstrated in the nested_prefab example. */ + auto BasePrefab = ecs.prefab("BasePrefab") + .set<Position>({10, 20}) + .add_owned<Position>(); // Ensure that component is always overridden + + auto SubPrefab1 = ecs.prefab("SubPrefab1") + .is_a(BasePrefab) + .set<Velocity>({1, 2}) + .add_owned<Velocity>(); + + auto SubPrefab2 = ecs.prefab("SubPrefab2") + .is_a(BasePrefab) + .set<Velocity>({3, 4}) + .add_owned<Velocity>(); + + /* Create new entities from prefabs */ + auto e1 = ecs.entity() + .is_a(SubPrefab1); + + auto e2 = ecs.entity() + .is_a(SubPrefab2); + + /* Print values of e1 */ + const Position *p = e1.get<Position>(); + const Velocity *v = e1.get<Velocity>(); + std::cout << "e1 Position: {" << p->x << ", " << p->y << "} (owned = " + << e1.owns<Position>() << "} " << std::endl; + std::cout << "e1 Velocity: {" << v->x << ", " << v->y << "} (owned = " + << e1.owns<Velocity>() << "} " << std::endl; + + /* Print values of e2 */ + p = e2.get<Position>(); + v = e2.get<Velocity>(); + std::cout << "e2 Position: {" << p->x << ", " << p->y << "} (owned = " + << e2.owns<Position>() << "} " << std::endl; + std::cout << "e2 Velocity: {" << v->x << ", " << v->y << "} (owned = " + << e2.owns<Velocity>() << "} " << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/include/nested_prefab.h b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/include/nested_prefab.h new file mode 100644 index 0000000000000000000000000000000000000000..cae5634ea79fdcec260f4a37e93c9c2494f1df63 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/include/nested_prefab.h @@ -0,0 +1,16 @@ +#ifndef NESTED_PREFAB_H +#define NESTED_PREFAB_H + +/* This generated file contains includes for project dependencies */ +#include "nested_prefab/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/include/nested_prefab/bake_config.h b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/include/nested_prefab/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..1df0d22038454a03eef001d58a86e1bf3306f3a2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/include/nested_prefab/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 NESTED_PREFAB_BAKE_CONFIG_H +#define NESTED_PREFAB_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/project.json b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/project.json new file mode 100644 index 0000000000000000000000000000000000000000..9896abfbe2565b272e0088f0ba727897c1e86ace --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/project.json @@ -0,0 +1,13 @@ +{ + "id": "nested_prefab", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..17cd99a7ccbc0a18708938179d01398e7b5970aa --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/18_nested_prefab/src/main.cpp @@ -0,0 +1,46 @@ +#include <nested_prefab.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create a prefab with a child entity. When this prefab is instantiated, + * the child will be instantiated too as a child of the instance. */ + auto Root = ecs.prefab("Root") + .set<Position>({10, 20}); + + /* Specify the RootPrefab as the parent for the nested prefab. This will + * cause the child prefab to be instantiated whenever an instanceof + * RootPrefab is created. */ + ecs.prefab("Child") + .child_of(Root) + .set<Position>({30, 40}); + + auto e = ecs.entity() + .is_a(Root); + + /* Lookup child in the instance we just created. This child will have e in + * its type with a CHILDOF mask, and the prefab Child in its type with an + * IsA pair. */ + auto child = e.lookup("Child"); + std::cout << "Child type = [" << child.type().str() << "]" << std::endl; + + /* Print position of e and of the child. Note that since we did not override + * any components, both position components are owned by the prefabs, not by + * the instances. */ + const Position *p = e.get<Position>(); + std::cout << "Position of e = {" << p->x << ", " << p->y << "}" << std::endl; + + p = child.get<Position>(); + std::cout << "Position of child = {" << p->x << ", " << p->y << "}" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/include/auto_override_nested_prefab.h b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/include/auto_override_nested_prefab.h new file mode 100644 index 0000000000000000000000000000000000000000..c4c9a1a625b66afd7042ac54c29e28f8a412eaea --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/include/auto_override_nested_prefab.h @@ -0,0 +1,16 @@ +#ifndef AUTO_OVERRIDE_NESTED_PREFAB_H +#define AUTO_OVERRIDE_NESTED_PREFAB_H + +/* This generated file contains includes for project dependencies */ +#include "auto_override_nested_prefab/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/include/auto_override_nested_prefab/bake_config.h b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/include/auto_override_nested_prefab/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..f3dc4baaf44852a59b1b59db24df4065fc2d8bd3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/include/auto_override_nested_prefab/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 AUTO_OVERRIDE_NESTED_PREFAB_BAKE_CONFIG_H +#define AUTO_OVERRIDE_NESTED_PREFAB_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/project.json b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/project.json new file mode 100644 index 0000000000000000000000000000000000000000..9a0e0d10a8087be386960723a638b71cc912a8e9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/project.json @@ -0,0 +1,13 @@ +{ + "id": "auto_override_nested_prefab", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..29a3be0053af34b9ad5e29b8947abb42cfc7a65d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/19_auto_override_nested_prefab/src/main.cpp @@ -0,0 +1,53 @@ +#include <auto_override_nested_prefab.h> +#include <iostream> + +struct Position { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create root prefab */ + auto RootPrefab = ecs.prefab("Root") + .set<Position>({10, 20}) + .add_owned<Position>(); + + /* Create child prefab. Instead of adding the child directly to + * Root, create a type that overrides the components from the + * ChildPrefab. This ensures that when the prefab is instantiated, the + * components from the child prefab are owned by the instance. */ + auto ChildPrefab = ecs.prefab("ChildPrefab") + .set<Position>({30, 40}); + + /* Instead of the ChildPrefab, add the Child type to RootPrefab. Use a + * string-based type expression to create the type, as the type needs to + * be fully constructed before registering it with the prefab parent. */ + ecs.prefab("Child") + .child_of(RootPrefab) + .is_a(ChildPrefab); + + /* Create new entity from Root. */ + auto e = ecs.entity().is_a(RootPrefab); + + /* Lookup child in the instance we just created. This child will have e in + * its type with a ChildOF relation, and the prefab ChildPrefab in its type with + * an IsA relation. Note how the identifier is Child, not ChildPrefab. */ + auto child = e.lookup("Child"); + std::cout << "Child type = [" << child.type().str() << "]" << std::endl; + + /* Print position of e and of the child. Note that since types were used to + * automatically override the components, the components are owned by both + * e and child. */ + const Position *p = e.get<Position>(); + std::cout << "Position of e = {" << p->x << ", " << p->y << "} (owned = " + << e.owns<Position>() << ")" + << std::endl; + + p = child.get<Position>(); + std::cout << "Position of child = {" << p->x << ", " << p->y << "} (owned = " + << child.owns<Position>() << ")" + << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/20_nested_variant/include/nested_variant.h b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/include/nested_variant.h new file mode 100644 index 0000000000000000000000000000000000000000..61af956cbf1ee5a01dad62de0fcd00b0c91076fb --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/include/nested_variant.h @@ -0,0 +1,16 @@ +#ifndef NESTED_VARIANT_H +#define NESTED_VARIANT_H + +/* This generated file contains includes for project dependencies */ +#include "nested_variant/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/20_nested_variant/include/nested_variant/bake_config.h b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/include/nested_variant/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..22ec5839bef46ea3c9d5d597dfe6727d19cea092 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/include/nested_variant/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 NESTED_VARIANT_BAKE_CONFIG_H +#define NESTED_VARIANT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/20_nested_variant/project.json b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/project.json new file mode 100644 index 0000000000000000000000000000000000000000..b65ce7ba0f7af88f98931ca32308ce5587683c0a --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/project.json @@ -0,0 +1,13 @@ +{ + "id": "nested_variant", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/20_nested_variant/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e059125300d35c710441a6893e1db06dc382f5ca --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/20_nested_variant/src/main.cpp @@ -0,0 +1,66 @@ +#include <nested_variant.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create a base prefab which will be inherited from by a child prefab */ + auto ChildBase = ecs.prefab("ChildBase") + .set<Position>({15, 25}); + + /* Create the root of the prefab hierarchy */ + auto RootPrefab = ecs.prefab("RootPrefab") + .set<Position>({10, 20}); + + /* Create two child prefabs that inherit from ChildBase */ + ecs.prefab("Child1") + .child_of(RootPrefab) + .is_a(ChildBase) + .set<Velocity>({30, 40}); + + ecs.prefab("Child2") + .child_of(RootPrefab) + .is_a(ChildBase) + .set<Velocity>({50, 60}); + + /* Create instance of Root */ + auto e = ecs.entity() + .is_a(RootPrefab); + + /* Print types of child1 and child2 */ + auto child1 = e.lookup("Child1"); + std::cout << "Child1 type = [" << child1.type().str() << "]" << std::endl; + + auto child2 = e.lookup("Child2"); + std::cout << "Child2 type = [" << child2.type().str() << "]" << std::endl; + + /* e shares Position from RootPrefab */ + e.get([](const Position& p) { + std::cout << "Position of e = {" << p.x << ", " << p.y << "}" + << std::endl; + }); + + /* Children will share Position from ChildBase and Velocity from the Child1 + * and Child2 prefabs respectively */ + child1.get([](const Position& p, const Velocity& v) { + std::cout << "Child1 Position = {" << p.x << ", " << p.y << "} " + << "Velocity = {" << v.x << ", " << v.y << "}" + << std::endl; + }); + + child2.get([](const Position& p, const Velocity& v) { + std::cout << "Child2 Position = {" << p.x << ", " << p.y << "} " + << "Velocity = {" << v.x << ", " << v.y << "}" + << std::endl; + }); +} diff --git a/fggl/ecs2/flecs/examples/cpp/21_system_features/include/system_features.h b/fggl/ecs2/flecs/examples/cpp/21_system_features/include/system_features.h new file mode 100644 index 0000000000000000000000000000000000000000..ba4bb2c960286d944ea1b6153c2fe02c8aa87b28 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/21_system_features/include/system_features.h @@ -0,0 +1,16 @@ +#ifndef SYSTEM_FEATURES_H +#define SYSTEM_FEATURES_H + +/* This generated file contains includes for project dependencies */ +#include "system_features/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/21_system_features/include/system_features/bake_config.h b/fggl/ecs2/flecs/examples/cpp/21_system_features/include/system_features/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4a326c0bd05f44639f497d8ad9532b005713f215 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/21_system_features/include/system_features/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SYSTEM_FEATURES_BAKE_CONFIG_H +#define SYSTEM_FEATURES_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/21_system_features/project.json b/fggl/ecs2/flecs/examples/cpp/21_system_features/project.json new file mode 100644 index 0000000000000000000000000000000000000000..6e923a9fad3190f7ca243ac78311a15d44ba7f7a --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/21_system_features/project.json @@ -0,0 +1,13 @@ +{ + "id": "system_features", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/21_system_features/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/21_system_features/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e10f4ab4b2e2b6ff2f69fe4fcff8697ebc82e149 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/21_system_features/src/main.cpp @@ -0,0 +1,63 @@ +#include <system_features.h> +#include <iostream> + +void Action(flecs::iter& it) { + std::cout << it.system().name() << " called!" << std::endl; +} + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create dummy systems */ + auto SystemA = ecs.system<>("SystemA").iter(Action); + auto SystemB = ecs.system<>("SystemB").iter(Action); + auto SystemC = ecs.system<>("SystemC").iter(Action); + auto SystemD = ecs.system<>("SystemD").iter(Action); + auto SystemE = ecs.system<>("SystemE").iter(Action); + + /* Create two features, each with a set of systems. Features are regular + * types, and the name feature is just a convention to indicate that a type + * only contains systems. Since systems, just like components, are stored as + * entities, they can be contained by types. */ + auto Feature1 = ecs.type("Feature1") + .add(SystemA) + .add(SystemB); + + auto Feature2 = ecs.type("Feature2") + .add(SystemC) + .add(SystemD); + + /* Create a feature that includes Feature2 and SystemE. Types/features can + * be organized in hierarchies */ + auto Feature3 = ecs.type("Feature3") + .add(Feature2) + .add(SystemE); + + /* First, disable Feature1 and Feature3. No systems will be executed. */ + std::cout << "Disable Feature1, Feature3" << std::endl; + Feature1.disable(); + Feature3.disable(); + ecs.progress(); + + /* Enable Feature3. SystemC, SystemD and SystemE will be executed */ + std::cout << std::endl << "Enable Feature3" << std::endl; + Feature3.enable(); + ecs.progress(); + + /* Disable Feature2. SystemE will be executed */ + std::cout << std::endl << "Disable Feature2" << std::endl; + Feature2.disable(); + ecs.progress(); + + /* Enable Feature1. SystemA, SystemB and SystemE will be executed. */ + std::cout << std::endl << "Enable Feature1" << std::endl; + Feature1.enable(); + ecs.progress(); + + /* Disable only SystemE */ + std::cout << std::endl << "Disable SystemE" << std::endl; + SystemE.disable(); + ecs.progress(); +} diff --git a/fggl/ecs2/flecs/examples/cpp/22_optional/include/optional.h b/fggl/ecs2/flecs/examples/cpp/22_optional/include/optional.h new file mode 100644 index 0000000000000000000000000000000000000000..1629d71273cbbeba44dcd2b8bca31b4187e46521 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/22_optional/include/optional.h @@ -0,0 +1,16 @@ +#ifndef OPTIONAL_H +#define OPTIONAL_H + +/* This generated file contains includes for project dependencies */ +#include "optional/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/22_optional/include/optional/bake_config.h b/fggl/ecs2/flecs/examples/cpp/22_optional/include/optional/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..08e1eb492a390264161617c9e45d87c4b9858979 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/22_optional/include/optional/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OPTIONAL_BAKE_CONFIG_H +#define OPTIONAL_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/22_optional/project.json b/fggl/ecs2/flecs/examples/cpp/22_optional/project.json new file mode 100644 index 0000000000000000000000000000000000000000..96c1db066df6e759faaa8cfb98ea4c4f9ff81d74 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/22_optional/project.json @@ -0,0 +1,13 @@ +{ + "id": "optional", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/22_optional/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/22_optional/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e421e9d2d8bab4c1002f602e36debf7331c3d2b8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/22_optional/src/main.cpp @@ -0,0 +1,55 @@ +#include <optional.h> +#include <iostream> + +struct Health { + double value; +}; + +struct Stamina { + double value; +}; + +struct Mana { + double value; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + /* Create system with three optional columns. Pointer template arguments are + * converted to components with the optional operator. + * + * This is an extreme example where all components are optional, which means + * that this system will evaluate all entities. */ + ecs.system<Health*, Stamina*, Mana*>() + .each([](flecs::entity e, Health* h, Stamina *s, Mana *m) { + if (h) { + h->value ++; + std::cout << e.name() << " process health" << std::endl; + } + + if (s) { + s->value ++; + std::cout << e.name() << " process stamina" << std::endl; + } + + if (m) { + m->value ++; + std::cout << e.name() << " process mana" << std::endl; + } + }); + + /* Create three entities that will all match with the Regenerate system */ + ecs.entity("HealthEntity").set<Health>({0}); + ecs.entity("StaminaEntity").set<Stamina>({0}); + ecs.entity("ManaEntity").set<Mana>({0}); + + ecs.set_target_fps(1); + + std::cout << "Application optional is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/23_get_children/include/get_children.h b/fggl/ecs2/flecs/examples/cpp/23_get_children/include/get_children.h new file mode 100644 index 0000000000000000000000000000000000000000..b57331fedb9ba7e4d93124e2b18647cfef79eaf0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/23_get_children/include/get_children.h @@ -0,0 +1,16 @@ +#ifndef GET_CHILDREN_H +#define GET_CHILDREN_H + +/* This generated file contains includes for project dependencies */ +#include "get_children/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/23_get_children/include/get_children/bake_config.h b/fggl/ecs2/flecs/examples/cpp/23_get_children/include/get_children/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..11cf900af96af3898ee9f5a7b39ea68f8cb392b2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/23_get_children/include/get_children/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 GET_CHILDREN_BAKE_CONFIG_H +#define GET_CHILDREN_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/23_get_children/project.json b/fggl/ecs2/flecs/examples/cpp/23_get_children/project.json new file mode 100644 index 0000000000000000000000000000000000000000..ad0852c2e3d46fe31da84a3d294a59f62be35844 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/23_get_children/project.json @@ -0,0 +1,13 @@ +{ + "id": "get_children", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/23_get_children/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/23_get_children/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1925e0d9c36d46c5439e09768c1725d0c0f92c33 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/23_get_children/src/main.cpp @@ -0,0 +1,36 @@ +#include <get_children.h> +#include <iostream> + +void print_tree(flecs::entity entity) +{ + static size_t indent = 0; + + std::cout << std::string(indent * 2, ' ') << entity.name() << std::endl; + + indent ++; + + for (auto children : entity.children()) { + for (auto i : children) { + // Print the child, and recursively iterate + print_tree(children.entity(i)); + } + } + + indent --; +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Create a simple hierarchy with 2 levels + auto parent = ecs.entity("Parent").scope([&]{ + ecs.entity("Child1").scope([&]{ + ecs.entity("GrandChild"); + }); + + ecs.entity("Child2"); + ecs.entity("Child3"); + }); + + print_tree(parent); +} diff --git a/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/include/on_demand_system.h b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/include/on_demand_system.h new file mode 100644 index 0000000000000000000000000000000000000000..0940bf26f9742e8684d8a20b93fc79075d936920 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/include/on_demand_system.h @@ -0,0 +1,16 @@ +#ifndef ON_DEMAND_SYSTEM_H +#define ON_DEMAND_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "on_demand_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/include/on_demand_system/bake_config.h b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/include/on_demand_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b97d48c43594398ddd5856b926ee2ece76258680 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/include/on_demand_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 ON_DEMAND_SYSTEM_BAKE_CONFIG_H +#define ON_DEMAND_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/project.json b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..9b0fe797acd3871d0fc516dd6b4320483521e60f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/project.json @@ -0,0 +1,13 @@ +{ + "id": "on_demand_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bac231ffa74a587d6bb89f784d385041187a1e3c --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/24_on_demand_system/src/main.cpp @@ -0,0 +1,79 @@ +#include <on_demand_system.h> +#include <iostream> + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +struct Printable {}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.component<Position>(); + ecs.component<Velocity>(); + + /* The 'Move' is marked as on_demand which means Flecs will only + * run this system if there is interest in any of its [out] columns. In this + * case the system will only be ran if there is interest in Position. */ + ecs.system<>().on_demand() + .term<Position>().inout(flecs::Out) + .term<Velocity>().inout(flecs::In) + .iter([](flecs::iter& it){ + auto p = it.term<Position>(1); + auto v = it.term<const Velocity>(2); + + for (auto row: it) { + p[row].x += v[row].x; + p[row].y += v[row].y; + + std::cout << "Moved " << it.entity(row).name() << " to {" << + p[row].x << ", " << p[row].y << "}" << std::endl; + } + }); + + /* The 'PrintPosition' is a regular system with an [in] (const) column. This signals + * that the system will not write Position, and relies on another system to + * provide a value for it. If there are any OnDemand systems that provide + * 'Position' as an output, they will be enabled. */ + auto PrintPosition = ecs.system<const Position, Printable>() + .each([](flecs::entity e, const Position& p, Printable&) { + std::cout << "Position of " << e.name() + << " is {" << p.x << ", " << p.y << "}" + << std::endl; + }); + + /* Create dummy entity. Entity does not match with PrintPosition because it + * does not have the Printable component */ + auto e = ecs.entity("MyEntity") + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + /* No systems will be executed. The PrintPosition system is enabled, but it + * has no matching entities. As a result, there is no demand for the + * Position component, and the Move system won't be executed either, even + * though the entity does match with it. */ + std::cout << "First iteration: PrintPosition is inactive" << std::endl; + ecs.progress(); + + /* Add Printable to the entity */ + e.add<Printable>(); + + /* Both systems will now be executed. The entity matches with PrintPosition + * meaning there is demand for Position, and thus the Move system will be + * enabled. */ + std::cout << std::endl << "Second iteration: PrintPosition is active" << std::endl; + ecs.progress(); + + /* Disable the PrintPosition system. Now there is no longer demand for the + * Position component, so the Move on-demand system will be disabled. */ + std::cout << std::endl << "Third iteration: PrintPosition is disabled" << std::endl; + PrintPosition.disable(); + ecs.progress(); +} diff --git a/fggl/ecs2/flecs/examples/cpp/25_snapshot/include/snapshot.h b/fggl/ecs2/flecs/examples/cpp/25_snapshot/include/snapshot.h new file mode 100644 index 0000000000000000000000000000000000000000..5a1b0ca48b326564f87cbfdde0680bb7ab08889e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/25_snapshot/include/snapshot.h @@ -0,0 +1,16 @@ +#ifndef SNAPSHOT_H +#define SNAPSHOT_H + +/* This generated file contains includes for project dependencies */ +#include "snapshot/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/25_snapshot/include/snapshot/bake_config.h b/fggl/ecs2/flecs/examples/cpp/25_snapshot/include/snapshot/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..8790fd39d2ebee904ea7eb803e31248873499b6d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/25_snapshot/include/snapshot/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SNAPSHOT_BAKE_CONFIG_H +#define SNAPSHOT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/25_snapshot/project.json b/fggl/ecs2/flecs/examples/cpp/25_snapshot/project.json new file mode 100644 index 0000000000000000000000000000000000000000..dd2cea007cd69d910c4abb45896fe2e023272c26 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/25_snapshot/project.json @@ -0,0 +1,13 @@ +{ + "id": "snapshot", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/25_snapshot/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/25_snapshot/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e4fd76cb24edafe128d9075346a2de35e465db4 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/25_snapshot/src/main.cpp @@ -0,0 +1,45 @@ +#include <snapshot.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + std::cout << "Moved " << e.name() << " to {" << + p.x << ", " << p.y << "}" << std::endl; + }); + + ecs.entity("MyEntity") + .set<Position>({0, 0}) + .set<Velocity>({1, 1}); + + /* Take a snapshot of the world */ + std::cout << "Take snapshot" << std::endl; + auto s = ecs.snapshot(); + s.take(); + + /* Progress the world a few times, updates position */ + ecs.progress(); + ecs.progress(); + ecs.progress(); + + /* Restore snapshot */ + std::cout << std::endl << "Restore snapshot" << std::endl; + s.restore(); + + ecs.progress(); +} diff --git a/fggl/ecs2/flecs/examples/cpp/27_filter_iter/include/filter_iter.h b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/include/filter_iter.h new file mode 100644 index 0000000000000000000000000000000000000000..0dce267ae9d1e744874b537a538a5b1336efd470 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/include/filter_iter.h @@ -0,0 +1,16 @@ +#ifndef FILTER_ITER_H +#define FILTER_ITER_H + +/* This generated file contains includes for project dependencies */ +#include "filter_iter/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/27_filter_iter/include/filter_iter/bake_config.h b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/include/filter_iter/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..9f496344c9f392df34e4aa3b8bb0e0c10c513bda --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/include/filter_iter/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 FILTER_ITER_BAKE_CONFIG_H +#define FILTER_ITER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/27_filter_iter/project.json b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/project.json new file mode 100644 index 0000000000000000000000000000000000000000..57915c883cf72d8f0fbe88c787696e31eecff630 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/project.json @@ -0,0 +1,13 @@ +{ + "id": "filter_iter", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/27_filter_iter/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0fde263e737e6bd4a3d4de986362cf6aef2789cc --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/27_filter_iter/src/main.cpp @@ -0,0 +1,33 @@ +#include <filter_iter.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + ecs.entity("e1") + .set<Position>({10, 20}) + .set<Velocity>({1, 1}); + + ecs.entity("e2") + .set<Position>({30, 40}) + .set<Velocity>({1, 1}); + + ecs.entity("e3") + .set<Position>({40, 50}); + + auto f = ecs.filter<Position, Velocity>(); + + f.each([&](flecs::entity e, Position& p, Velocity&) { + std::cout << "Matched " << e.name() << ": {" + << p.x << ", " << p.y << "}" << std::endl; + }); +} diff --git a/fggl/ecs2/flecs/examples/cpp/29_world_iter/include/world_iter.h b/fggl/ecs2/flecs/examples/cpp/29_world_iter/include/world_iter.h new file mode 100644 index 0000000000000000000000000000000000000000..042096c5ed957bfac9582ff4a3aaef8d3a4a761f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/29_world_iter/include/world_iter.h @@ -0,0 +1,16 @@ +#ifndef WORLD_ITER_H +#define WORLD_ITER_H + +/* This generated file contains includes for project dependencies */ +#include "world_iter/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/29_world_iter/include/world_iter/bake_config.h b/fggl/ecs2/flecs/examples/cpp/29_world_iter/include/world_iter/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..7aaa9a440521629a441f0e0ea24c1784c86683a7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/29_world_iter/include/world_iter/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 WORLD_ITER_BAKE_CONFIG_H +#define WORLD_ITER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/29_world_iter/project.json b/fggl/ecs2/flecs/examples/cpp/29_world_iter/project.json new file mode 100644 index 0000000000000000000000000000000000000000..328eb7b2571564afa4bd3f9bf648bc1eb6f7794d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/29_world_iter/project.json @@ -0,0 +1,13 @@ +{ + "id": "world_iter", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/29_world_iter/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/29_world_iter/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..594d71310757e28f26af837993fa874b964fcbf4 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/29_world_iter/src/main.cpp @@ -0,0 +1,39 @@ +#include <world_iter.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.entity("E1") + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.entity("E2") + .set<Position>({30, 40}) + .set<Velocity>({3, 4}); + + // 1: Iterate all entities with Position, Velocity in the world + ecs.each([](flecs::entity e, Position& p, Velocity& v) { + std::cout << "Matched " << e.name() << ": Position = {" + << p.x << ", " << p.y << "} Velocity = {" + << v.x << ", " << v.y << "}" << std::endl; + }); + + // 2: Iterate all entities in the world + for (auto it : ecs) { + flecs::type table_type = it.table_type(); + std::cout << "Iterating table [" << table_type.str() << "]" + << " (" << it.count() << " entities)" << std::endl; + } +} diff --git a/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/include/snapshot_iter.h b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/include/snapshot_iter.h new file mode 100644 index 0000000000000000000000000000000000000000..d35782750e08a292e7d02c2c732d4ea6e97af1f1 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/include/snapshot_iter.h @@ -0,0 +1,16 @@ +#ifndef SNAPSHOT_ITER_H +#define SNAPSHOT_ITER_H + +/* This generated file contains includes for project dependencies */ +#include "snapshot_iter/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/include/snapshot_iter/bake_config.h b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/include/snapshot_iter/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..ca5cc5d3cb6c054ac9fc673d7e70c7882044190b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/include/snapshot_iter/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SNAPSHOT_ITER_BAKE_CONFIG_H +#define SNAPSHOT_ITER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/project.json b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/project.json new file mode 100644 index 0000000000000000000000000000000000000000..e749023fa610f622d271081670127a8a7021e72b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/project.json @@ -0,0 +1,13 @@ +{ + "id": "snapshot_iter", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5db22280641336ac04a432c4e49283d486a731e5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/30_snapshot_iter/src/main.cpp @@ -0,0 +1,38 @@ +#include <snapshot_iter.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.entity("E1") + .set<Position>({10, 20}) + .set<Velocity>({1, 1}); + + ecs.entity("E2") + .set<Position>({30, 40}) + .set<Velocity>({1, 1}); + + /* Create filter that matches all entities with [Position, Velocity] */ + auto f = ecs.filter<Position, Velocity>(); + + /* Take a filtered snapshot of the current state */ + auto s = ecs.snapshot(); + s.take(f); + + for (auto it : s) { + flecs::type table_type = it.table_type(); + std::cout << "Iterating table [" << table_type.str() << "]" + << " (" << it.count() << " entities)" << std::endl; + } +} diff --git a/fggl/ecs2/flecs/examples/cpp/34_persistent_query/.gitignore b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fff6aadd6112352fa355dd19be6a8678b11dfdc0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/.gitignore @@ -0,0 +1,5 @@ +.bake_cache +.DS_Store +.vscode +gcov +bin diff --git a/fggl/ecs2/flecs/examples/cpp/34_persistent_query/include/persistent_query.h b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/include/persistent_query.h new file mode 100644 index 0000000000000000000000000000000000000000..18a9325b6672c91b1996a5360f5945345425a951 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/include/persistent_query.h @@ -0,0 +1,16 @@ +#ifndef PERSISTENT_QUERY_H +#define PERSISTENT_QUERY_H + +/* This generated file contains includes for project dependencies */ +#include "persistent_query/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/34_persistent_query/include/persistent_query/bake_config.h b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/include/persistent_query/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..590dc80784f8ae8cd7ed0a782d52a11183ceb555 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/include/persistent_query/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PERSISTENT_QUERY_BAKE_CONFIG_H +#define PERSISTENT_QUERY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/34_persistent_query/project.json b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/project.json new file mode 100644 index 0000000000000000000000000000000000000000..f01ce52940caee1752dd8c0a6b8b4c0597af9665 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/project.json @@ -0,0 +1,12 @@ +{ + "id": "persistent_query", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/34_persistent_query/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8d4a91cd44ae338e2a802fd5d6369e8da35bed14 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/34_persistent_query/src/main.cpp @@ -0,0 +1,73 @@ +#include <persistent_query.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + // Create the world, pass arguments for overriding the number of threads,fps + // or for starting the admin dashboard (see flecs.h for details). + flecs::world ecs(argc, argv); + + // Create a query. Queries are 'persistent' meaning they are registered with + // the world and continuously matched with new entities (tables). Queries + // are the fastest way to iterate over entities, as a lot of processing is + // done when entities are matched, outside of the main loop. + // + // Queries are the mechanism used by systems, and as such both accept the + // same signature expressions, and have similar performance. + auto q = ecs.query<Position, const Velocity>(); + + /* Create a few entities that match the query */ + ecs.entity("E1") + .set<Position>({1, 2}) + .set<Velocity>({1, 1}); + + ecs.entity("E2") + .set<Position>({3, 4}) + .set<Velocity>({1, 1}); + + // Don't add Velocity here, E3 will not match query + ecs.entity("E3") + .set<Position>({5, 6}); + + // Iterate over entities matching the query using each + q.each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + + std::cout << "Moved " << e.name() << " to {" << + p.x << ", " << p.y << "}" << std::endl; + }); + + // If the entity is not needed, it can be ommitted + q.each([](Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + // Iterate over entities matching the query using iter + q.iter([](flecs::iter it, Position* p, const Velocity* v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + + std::cout << "Moved " << it.entity(i).name() << " to {" << + p[i].x << ", " << p[i].y << "}" << std::endl; + } + }); + + // If only the iterator is needed, the components can be ommitted: + size_t count = 0; + q.iter([&](flecs::iter it) { + count += it.count(); // Count the number of matched entities + }); + + std::cout << "Query matched with " << count << " entities" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/.gitignore b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fff6aadd6112352fa355dd19be6a8678b11dfdc0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/.gitignore @@ -0,0 +1,5 @@ +.bake_cache +.DS_Store +.vscode +gcov +bin diff --git a/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/include/empty_system_signature.h b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/include/empty_system_signature.h new file mode 100644 index 0000000000000000000000000000000000000000..ee1a31606d5c667b06859cf5a4800540ec3a5be2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/include/empty_system_signature.h @@ -0,0 +1,16 @@ +#ifndef EMPTY_SYSTEM_SIGNATURE_H +#define EMPTY_SYSTEM_SIGNATURE_H + +/* This generated file contains includes for project dependencies */ +#include "empty_system_signature/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/include/empty_system_signature/bake_config.h b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/include/empty_system_signature/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..5faa907960cd180f56ab31431adcc4747b63d1c8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/include/empty_system_signature/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 EMPTY_SYSTEM_SIGNATURE_BAKE_CONFIG_H +#define EMPTY_SYSTEM_SIGNATURE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/project.json b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/project.json new file mode 100644 index 0000000000000000000000000000000000000000..2730653e2b64b3270104cac0bc17c2e3e20484bc --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/project.json @@ -0,0 +1,13 @@ +{ + "id": "empty_system_signature", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..07973e1c408ae15adf033398365ee049a2865616 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/35_empty_system_signature/src/main.cpp @@ -0,0 +1,20 @@ +#include <empty_system_signature.h> +#include <iostream> + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + /* Define a system with an empty signature. Systems that do not match with + * any entities are invoked once per frame */ + ecs.system<>() + .iter([](flecs::iter&) { + std::cout << "System invoked!" << std::endl; + }); + + ecs.set_target_fps(1); + + std::cout << "Application simple_system is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/36_periodic_system/.gitignore b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fff6aadd6112352fa355dd19be6a8678b11dfdc0 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/.gitignore @@ -0,0 +1,5 @@ +.bake_cache +.DS_Store +.vscode +gcov +bin diff --git a/fggl/ecs2/flecs/examples/cpp/36_periodic_system/include/periodic_system.h b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/include/periodic_system.h new file mode 100644 index 0000000000000000000000000000000000000000..4793c25821ca1249661f7a057351452598cf716c --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/include/periodic_system.h @@ -0,0 +1,16 @@ +#ifndef PERIODIC_SYSTEM_H +#define PERIODIC_SYSTEM_H + +/* This generated file contains includes for project dependencies */ +#include "periodic_system/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/36_periodic_system/include/periodic_system/bake_config.h b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/include/periodic_system/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..fbfcc181aa5ab51d0e72c7da5b6801b47756afe8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/include/periodic_system/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PERIODIC_SYSTEM_BAKE_CONFIG_H +#define PERIODIC_SYSTEM_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/36_periodic_system/project.json b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/project.json new file mode 100644 index 0000000000000000000000000000000000000000..fe62c423249edb5bcad3de6b6e50e376c6c1e82e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/project.json @@ -0,0 +1,12 @@ +{ + "id": "periodic_system", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/36_periodic_system/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad57ec9cc0186b76f2b9fce9a9cea06545604014 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/36_periodic_system/src/main.cpp @@ -0,0 +1,43 @@ +#include <periodic_system.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world ecs(argc, argv); + + ecs.system<Position, Velocity>() + .each([](Position& p, Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + /* Create system that is invoked once per second */ + ecs.system<Position>() + .interval(1.0) + .each([](flecs::entity e, Position& p) { + std::cout << "Position of " << e.name() << " is {" << + p.x << ", " << p.y << "}" << std::endl; + }); + + ecs.entity("MyEntity") + .set<Position>({0, 0}) + .set<Velocity>({1, 1}); + + /* Run all normal systems at 60FPS */ + ecs.set_target_fps(60); + + std::cout << "Application move_system is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/37_delta_time/include/delta_time.h b/fggl/ecs2/flecs/examples/cpp/37_delta_time/include/delta_time.h new file mode 100644 index 0000000000000000000000000000000000000000..7abc5c609e385247bb82df21cfcd54ed5dd264f2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/37_delta_time/include/delta_time.h @@ -0,0 +1,16 @@ +#ifndef DELTA_TIME_H +#define DELTA_TIME_H + +/* This generated file contains includes for project dependencies */ +#include "delta_time/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/37_delta_time/include/delta_time/bake_config.h b/fggl/ecs2/flecs/examples/cpp/37_delta_time/include/delta_time/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..db5c45b6a8f7aee32b417ba90c181814201a0ddc --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/37_delta_time/include/delta_time/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DELTA_TIME_BAKE_CONFIG_H +#define DELTA_TIME_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/37_delta_time/project.json b/fggl/ecs2/flecs/examples/cpp/37_delta_time/project.json new file mode 100644 index 0000000000000000000000000000000000000000..69e474dcdf366e4daac33d06e8b62257db914f75 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/37_delta_time/project.json @@ -0,0 +1,13 @@ +{ + "id": "delta_time", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/37_delta_time/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/37_delta_time/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0d105fc073c907d0ac8943910d2126a1b1d333d8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/37_delta_time/src/main.cpp @@ -0,0 +1,51 @@ +#include <delta_time.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + ecs.system<Position, Velocity>() + .each([](flecs::entity e, Position& p, Velocity& v) { + // Use delta_time to update the entity proportionally to the amount + // of time that has passed inbetween frames. The delta_time value is + // the same for each system when computing a frame. + // + // The delta_time value obtained through the entity comes from the + // world and is the value passed into world.progress or, if no value + // is provided, the value that flecs measured automatically. + p.x += v.x * static_cast<double>(e.delta_time()); + p.y += v.y * static_cast<double>(e.delta_time()); + + std::cout << "Moved " << e.name() << " to {" << + p.x << ", " << p.y << "}" << std::endl; + }); + + ecs.entity("MyEntity") + .set<Position>({0, 0}) + .set<Velocity>({1, 1}); + + // Set target FPS for main loop to 60 frames per second. The specified FPS + // is not a guarantee, which is why applications should use delta_time to + // progress a simulation, which contains the actual time passed. + ecs.set_target_fps(60); + + std::cout << "Application delta_time is running, press CTRL-C to exit..." + << std::endl; + + // Run systems. When an application does not provide an explicit value for + // delta_time flecs will measure the delta_time automatically inbetween + // frames. + // + // For deterministic simulations, applications will want to pass in a fixed + // value that does not rely on the system clock. + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/include/delta_system_time.h b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/include/delta_system_time.h new file mode 100644 index 0000000000000000000000000000000000000000..df4ff6072676ba7656a6040352d13e1be6b2ad38 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/include/delta_system_time.h @@ -0,0 +1,16 @@ +#ifndef DELTA_SYSTEM_TIME_H +#define DELTA_SYSTEM_TIME_H + +/* This generated file contains includes for project dependencies */ +#include "delta_system_time/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/include/delta_system_time/bake_config.h b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/include/delta_system_time/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..78b31a7882154ccf83cca2008fef5584bf708d0a --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/include/delta_system_time/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DELTA_SYSTEM_TIME_BAKE_CONFIG_H +#define DELTA_SYSTEM_TIME_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/project.json b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/project.json new file mode 100644 index 0000000000000000000000000000000000000000..6b04d23ccb041534f888b2836bf0cb5dcd8f490f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/project.json @@ -0,0 +1,13 @@ +{ + "id": "delta_system_time", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9cd5fdfe2ebfb6f786fbc9773ee67084f18fa2a5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/38_delta_system_time/src/main.cpp @@ -0,0 +1,43 @@ +#include <delta_system_time.h> +#include <iostream> + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Create three systems with different periods, and print for each the + // delta_system_time. + // + // For systems with a period, the regular delta_time is not as useful as it + // provides the time passed since the last frame. The delta_system_time + // addresses this by keeping track of the time elapsed since the last time + // the system was invoked. + // + // This value should approximate the specified period, but may differ due to + // aliassing issues (the main loop still ticks at its own frequency) or due + // to variability introduced by operating system scheduling / system clock. + + ecs.system<>() + .interval(0.5) + .iter([](flecs::iter& it) { + std::cout << "t = 0.5, time elampsed = " << it.delta_system_time() + << std::endl; + }); + + ecs.system<>() + .interval(1.0) + .iter([](flecs::iter& it) { + std::cout << "t = 1.0, time elampsed = " << it.delta_system_time() + << std::endl; + }); + + ecs.system<>() + .interval(2.0) + .iter([](flecs::iter& it) { + std::cout << "t = 2.0, time elampsed = " << it.delta_system_time() + << std::endl; + }); + + ecs.set_target_fps(60); + + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/39_set/include/set.h b/fggl/ecs2/flecs/examples/cpp/39_set/include/set.h new file mode 100644 index 0000000000000000000000000000000000000000..70c09492ed6235998107f824d6fff2d04eae6538 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/39_set/include/set.h @@ -0,0 +1,16 @@ +#ifndef SET_H +#define SET_H + +/* This generated file contains includes for project dependencies */ +#include "set/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/39_set/include/set/bake_config.h b/fggl/ecs2/flecs/examples/cpp/39_set/include/set/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a9c09deeff61fe56ecc939bc90b96024d778e96e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/39_set/include/set/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SET_BAKE_CONFIG_H +#define SET_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/39_set/project.json b/fggl/ecs2/flecs/examples/cpp/39_set/project.json new file mode 100644 index 0000000000000000000000000000000000000000..92b7e68cc35f2aa51fb1021ff14138a3e6048991 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/39_set/project.json @@ -0,0 +1,12 @@ +{ + "id": "set", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/39_set/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/39_set/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0a54179008f9f42440e4e1a5f5600d381454ce79 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/39_set/src/main.cpp @@ -0,0 +1,56 @@ +#include <set.h> +#include <iostream> + +// A nice side effect of using the lamvda set API is that we don't need to add +// non-default constructors that accept the members we want to set. + +struct Days { + Days() : value_(0) { } + int value_; +}; + +struct Weeks { + Weeks() : value_(0) { } + int value_; +}; + +struct Months { + Months() : value_(0) { } + int value_; +}; + +// Increment days, weeks, months +void inc(flecs::entity e) { + e.set([](Days& days, Weeks& weeks, Months& months) { + days.value_ ++; + + if (!(days.value_ % 7)) { + weeks.value_ ++; + } + + if (!(days.value_ % 30)) { + months.value_ ++; + } + }); +} + +int main(int argc, char *argv[]) { + // Create the world, pass arguments for overriding the number of threads,fps + // or for starting the admin dashboard (see flecs.h for details). + flecs::world ecs(argc, argv); + + // Create empty entity + auto e = ecs.entity(); + + // Increment a bunch of times + for (int i = 0; i < 40; i ++) { + inc(e); + } + + // Print output for three components + e.get([](const Days& d, const Weeks& w, const Months& m) { + std::cout << "days: " << d.value_ + << ", weeks: " << w.value_ + << ", months: " << m.value_ << std::endl; + }); +} diff --git a/fggl/ecs2/flecs/examples/cpp/40_task/include/task.h b/fggl/ecs2/flecs/examples/cpp/40_task/include/task.h new file mode 100644 index 0000000000000000000000000000000000000000..9b5cfbc8f185f86f7f87952b49756afe99d9f9ca --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/40_task/include/task.h @@ -0,0 +1,16 @@ +#ifndef TASK_H +#define TASK_H + +/* This generated file contains includes for project dependencies */ +#include "task/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/40_task/include/task/bake_config.h b/fggl/ecs2/flecs/examples/cpp/40_task/include/task/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..073f544886b8fa876ec39045f286c07baa32ab54 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/40_task/include/task/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 TASK_BAKE_CONFIG_H +#define TASK_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/40_task/project.json b/fggl/ecs2/flecs/examples/cpp/40_task/project.json new file mode 100644 index 0000000000000000000000000000000000000000..82ada2243764da9ed5cf9eda8a31fc66f5e25780 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/40_task/project.json @@ -0,0 +1,13 @@ +{ + "id": "task", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/40_task/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/40_task/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f2ab657308f4ce2ce17439f9cf98b2a8ec6337ee --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/40_task/src/main.cpp @@ -0,0 +1,43 @@ +#include <task.h> +#include <iostream> + +struct TaskContext { + int value; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + ecs.component<TaskContext>(); + + // Tasks are systems that are not matched with any entities. + + // Basic task + ecs.system<>() + .iter([](flecs::iter&) { + std::cout << "Task executed every second" << std::endl; + }); + + // Task that is executed every 2 seconds + ecs.system<>() + .interval(2.0) + .iter([](flecs::iter&) { + std::cout << "Task executed every 2 seconds" << std::endl; + }); + + // It is possible to add components to a task, just like regular systems + auto system = ecs.system<>(nullptr, "SYSTEM:TaskContext") + .iter([](flecs::iter& it) { + auto ctx = it.term<const TaskContext>(1); + std::cout << "Task with context: " << ctx->value << std::endl; + }); + + system.set<TaskContext>({10}); + + ecs.set_target_fps(1); + + std::cout << "Application task is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/41_sorting/include/sorting.h b/fggl/ecs2/flecs/examples/cpp/41_sorting/include/sorting.h new file mode 100644 index 0000000000000000000000000000000000000000..e509299d4b4b523c3bee0af16897a17afacf084e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/41_sorting/include/sorting.h @@ -0,0 +1,16 @@ +#ifndef SORTING_H +#define SORTING_H + +/* This generated file contains includes for project dependencies */ +#include "sorting/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/41_sorting/include/sorting/bake_config.h b/fggl/ecs2/flecs/examples/cpp/41_sorting/include/sorting/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..5b90027133592ea262a84de4696b59293fa3407c --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/41_sorting/include/sorting/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SORTING_BAKE_CONFIG_H +#define SORTING_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/41_sorting/project.json b/fggl/ecs2/flecs/examples/cpp/41_sorting/project.json new file mode 100644 index 0000000000000000000000000000000000000000..e84011ac74f4175b968b7d4bbca881db6c6fe480 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/41_sorting/project.json @@ -0,0 +1,13 @@ +{ + "id": "sorting", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/41_sorting/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/41_sorting/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..38bd533c23905fef7cecc0d3b1f2e0af6535de0a --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/41_sorting/src/main.cpp @@ -0,0 +1,67 @@ +#include <sorting.h> +#include <iostream> + +struct Position { + double x, y; +}; + +// Order by x member of Position */ +int compare_position( + flecs::entity_t e1, + const Position *p1, + flecs::entity_t e2, + const Position *p2) +{ + (void)e1; + (void)e2; + return (p1->x > p2->x) - (p1->x < p2->x); +} + +// Iterate query, printed values will be ordered +void print_query(flecs::query<Position>& q) { + q.each([](flecs::entity, Position& p) { + std::cout << "{" << p.x << "," << p.y << "}" << std::endl; + }); +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Create entities, set Position in random order + auto e = ecs.entity().set<Position>({1, 0}); + ecs.entity().set<Position>({6, 0}); + ecs.entity().set<Position>({2, 0}); + ecs.entity().set<Position>({5, 0}); + ecs.entity().set<Position>({4, 0}); + + // Create a system with sorting enabled + auto sys = ecs.system<Position>() + .order_by(compare_position) + .each([](Position &p) { + std::cout << "{" << p.x << "," << p.y << "}" << std::endl; + }); + + // Create a query for component Position + auto q = ecs.query<Position>(); + + // Order by Position component + q.order_by(compare_position); + + // Iterate query, print values of Position + std::cout << "-- First iteration" << std::endl; + print_query(q); + + // Change the value of one entity, invalidating the order + e.set<Position>({7, 0}); + + // Iterate query again, printed values are still ordered + std::cout << "-- Second iteration" << std::endl; + print_query(q); + + // Create new entity to show that data is also sorted for system + ecs.entity().set<Position>({3, 0}); + + // Run system, output will be sorted + std::cout << "-- System iteration" << std::endl; + sys.run(); +} diff --git a/fggl/ecs2/flecs/examples/cpp/42_pair/include/pair.h b/fggl/ecs2/flecs/examples/cpp/42_pair/include/pair.h new file mode 100644 index 0000000000000000000000000000000000000000..7e9f34c592c15e6732872928814a6f04dcf282a9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/42_pair/include/pair.h @@ -0,0 +1,16 @@ +#ifndef PAIR_H +#define PAIR_H + +/* This generated file contains includes for project dependencies */ +#include "pair/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/42_pair/include/pair/bake_config.h b/fggl/ecs2/flecs/examples/cpp/42_pair/include/pair/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..6a8c2d68f7befb672958636faea6f718243d374c --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/42_pair/include/pair/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PAIR_BAKE_CONFIG_H +#define PAIR_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/42_pair/project.json b/fggl/ecs2/flecs/examples/cpp/42_pair/project.json new file mode 100644 index 0000000000000000000000000000000000000000..d66a822d0ff94a7fab37db135716d53aaa31b8af --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/42_pair/project.json @@ -0,0 +1,13 @@ +{ + "id": "pair", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/42_pair/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/42_pair/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7bc7b38ef5a7ae7272df9322abd49fb816f09c78 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/42_pair/src/main.cpp @@ -0,0 +1,84 @@ +#include <pair.h> +#include <iostream> + +/* Ordinary position & velocity components */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +/* This component will be used as a relation. A relation can be added to an + * entity together with a relation object, which is the thing to which relation + * applies. In this case, the ExpiryTime relation will apply to a component that + * we want to remove after a timeout. */ +struct ExpiryTimer { + float expiry_time; + float t; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + /* Create a system that matches all entities with an ExpiryTimer relation */ + ecs.system<>() + .term<ExpiryTimer>(flecs::Wildcard) + .iter([](flecs::iter it) { + /* First, get the pair component */ + auto et = it.term<ExpiryTimer>(1); + + /* Get the id of the term, which contains the pair */ + flecs::entity pair = it.term_id(1); + + /* Obtain component id, which is the 'object' part of the pair */ + flecs::entity obj = pair.object(); + + for (auto i : it) { + /* Increase timer. When it equals expiry time, remove component */ + et[i].t += it.delta_time(); + if (et[i].t >= et[i].expiry_time) { + /* Remove both the component and the pair. If the pair + * would not be removed, the system would still be invoked + * after this. */ + std::cout << "Remove component " << obj.name() << std::endl; + + /* Removes component (Position or Velocity) */ + it.entity(i).remove(obj); + + /* Removes pair */ + it.entity(i).remove(pair); + } + } + }); + + /* Create an entity with Position and Velocity */ + auto e = ecs.entity() + .add<Position>() + .add<Velocity>(); + + /* Assign the pair to the Position component. After 3 seconds Position will + * be removed from the entity */ + e.set<ExpiryTimer, Position>({ 3, 0 }); + + /* Also assign the pair to the Velocity comopnent. After 2 seconds the + * Velocity component will be removed. */ + e.set<ExpiryTimer, Velocity>({ 2, 0 }); + + /* Note that the pair has been added to the same entity twice, which is not + * something that is ordinarily possible with a component. */ + + /* Run the main loop until both components have been removed */ + ecs.set_target_fps(1); + + std::cout << "Running..." << std::endl; + + /* Run systems */ + while (ecs.progress()) { + /* As soon as both components are removed, exit main loop */ + if (e.type().vector().count() == 0) { + break; + } + } +} diff --git a/fggl/ecs2/flecs/examples/cpp/44_switch/include/switch.h b/fggl/ecs2/flecs/examples/cpp/44_switch/include/switch.h new file mode 100644 index 0000000000000000000000000000000000000000..fc8750bbc2e2df28f5c37bc6ab3e6309fc82122d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/44_switch/include/switch.h @@ -0,0 +1,16 @@ +#ifndef SWITCH_H +#define SWITCH_H + +/* This generated file contains includes for project dependencies */ +#include "switch/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/44_switch/include/switch/bake_config.h b/fggl/ecs2/flecs/examples/cpp/44_switch/include/switch/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..02caf6be094dcfbeea9569e282c52ccb2437276d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/44_switch/include/switch/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SWITCH_BAKE_CONFIG_H +#define SWITCH_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/44_switch/project.json b/fggl/ecs2/flecs/examples/cpp/44_switch/project.json new file mode 100644 index 0000000000000000000000000000000000000000..5c2799f9609f186530c20dbc7639461a31fcf532 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/44_switch/project.json @@ -0,0 +1,12 @@ +{ + "id": "switch", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/44_switch/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/44_switch/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f245c37acb76ee2058383f559434a450e2f980ab --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/44_switch/src/main.cpp @@ -0,0 +1,93 @@ +#include <switch.h> +#include <iostream> + +// This example shows how to use switch and case roles, which let an application +// build efficient state machines. Without switch and case, an application would +// have to add a separate tag for each state, while ensuring that the previous +// state is removed. This is not only less usable, but also not very performant. +// +// With tags, different combinations of states can grow quickly, which can +// scatter entities across many different tables. With switches, entities with +// different states are still stored in the same table, which makes it much +// faster to change states, and improves iteration performance. +// +// Additionally, adding a case to an entity will remove the previous case, which +// makes the switch/case combination especially suited for state machihnes. +// + +struct Movement { + struct Walking { }; + struct Running { }; +}; + +struct Direction { + struct Front { }; + struct Back { }; + struct Left { }; + struct Right { }; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Register switch types so flecs knows which cases belong to which switch + ecs.type().component<Movement>() + .add<Movement::Walking>() + .add<Movement::Running>(); + + ecs.type().component<Direction>() + .add<Direction::Front>() + .add<Direction::Back>() + .add<Direction::Left>() + .add<Direction::Right>(); + + // Create a system that subscribes for all entities that have a Direction + // and that are walking + ecs.system<>("Walk") + .term<Movement::Walking>().role(flecs::Case) + .term<Direction>().role(flecs::Switch) + .iter([](const flecs::iter& it) { + // Get the column with direction states. This is stored as an array + // with identifiers to the individual states + auto movement = it.term<flecs::entity_t>(1); + auto direction = it.term<flecs::entity_t>(2); + + for (auto i : it) { + // Movement will always be Walking, Direction can be any state + std::cout << it.entity(i).name() + << ": Movement: " + << it.world().entity(movement[i]).name() + << ", Direction: " + << it.world().entity(direction[i]).name() + << std::endl; + } + }); + + // Create a few entities with various state combinations + ecs.entity("e1") + .add_switch<Movement>() + .add_case<Movement::Walking>() + .add_switch<Direction>() + .add_case<Direction::Front>(); + + ecs.entity("e2") + .add_switch<Movement>() + .add_case<Movement::Running>() + .add_switch<Direction>() + .add_case<Direction::Left>(); + + auto e3 = ecs.entity("e3") + .add_switch<Movement>() + .add_case<Movement::Running>() + .add_switch<Direction>() + .add_case<Direction::Back>(); + + // Add Walking to e4. This will remove the Running case + e3.add_case<Movement::Walking>(); + + // Set target FPS for main loop + ecs.set_target_fps(1); + + // Run system. e2 is filtered out because it's not in the Walking state + while (ecs.progress()) { } +} diff --git a/fggl/ecs2/flecs/examples/cpp/45_subquery/include/subquery.h b/fggl/ecs2/flecs/examples/cpp/45_subquery/include/subquery.h new file mode 100644 index 0000000000000000000000000000000000000000..31aa820866cb99923f8717734fdc355abc44967d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/45_subquery/include/subquery.h @@ -0,0 +1,16 @@ +#ifndef SUBQUERY_H +#define SUBQUERY_H + +/* This generated file contains includes for project dependencies */ +#include "subquery/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/45_subquery/include/subquery/bake_config.h b/fggl/ecs2/flecs/examples/cpp/45_subquery/include/subquery/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a354954b7550655236ccf5feb5b34cb1d6e49fed --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/45_subquery/include/subquery/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SUBQUERY_BAKE_CONFIG_H +#define SUBQUERY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/45_subquery/project.json b/fggl/ecs2/flecs/examples/cpp/45_subquery/project.json new file mode 100644 index 0000000000000000000000000000000000000000..d5c9b14591315575e2da907bcdebf313b775c990 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/45_subquery/project.json @@ -0,0 +1,12 @@ +{ + "id": "subquery", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/45_subquery/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/45_subquery/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e579bf4bc81b7a637ee7449c42639f3472c2963b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/45_subquery/src/main.cpp @@ -0,0 +1,49 @@ +#include <subquery.h> +#include <iostream> + +/* Subqueries allow applications to narrow down the potential set of tables to + * match with to a small size, which makes creation and deletion of queries + * efficient enough to do in the main loop. Additionally, subqueries have a + * lower adminstrative footprint than regular queries, which reduces the amount + * of memory that is required to store them. */ + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + ecs.entity("e1") + .add<Position>(); + + ecs.entity("e2") + .add<Position>() + .add<Velocity>(); + + ecs.entity("e3") + .add<Velocity>(); + + /* Create a parent query that subscribes for all entities with Position */ + auto q_parent = ecs.query<Position>(); + + /* Create a subquery that selects the subset of entities matched by q_parent + * that have the Velocity component. */ + auto q_sub = ecs.query<Velocity>(q_parent); + + /* Iterate the subquery. Note that only e2 is matched, since it has both + * Position and Velocity */ + q_sub.each([](flecs::entity e, Velocity&) { + std::cout << e.name() << " matched" << std::endl; + }); + + /* The subquery could also have subscribed itself for both Position and + * Velocity, in which case it would still only have matched against the + * entities of the parent query, but the result would have been harder to + * see as on the surface it would be the same . */ +} diff --git a/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/include/deferred_operations.h b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/include/deferred_operations.h new file mode 100644 index 0000000000000000000000000000000000000000..90279743a600699afec6a054ebfd249b3f4ab69e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/include/deferred_operations.h @@ -0,0 +1,16 @@ +#ifndef DEFERRED_OPERATIONS_H +#define DEFERRED_OPERATIONS_H + +/* This generated file contains includes for project dependencies */ +#include "deferred_operations/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/include/deferred_operations/bake_config.h b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/include/deferred_operations/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..7f292724e3f20a242ea62c1156c38e75517a6d40 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/include/deferred_operations/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DEFERRED_OPERATIONS_BAKE_CONFIG_H +#define DEFERRED_OPERATIONS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/project.json b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/project.json new file mode 100644 index 0000000000000000000000000000000000000000..bbdf640c77ffe41b5bce3e9748c805c94568c9c3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/project.json @@ -0,0 +1,13 @@ +{ + "id": "deferred_operations", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..702a7a3f7f819fdf8d0761a33d91c5e84deb40bb --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/47_deferred_operations/src/main.cpp @@ -0,0 +1,31 @@ +#include <deferred_operations.h> +#include <iostream> + +struct Position { + double x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + /* Create OnSet system so we can see when Velocity is actually set */ + ecs.system<Position>() + .kind(flecs::OnSet) + .each([](Position& p) { + std::cout << "Position set to {" << p.x << ", " << p.y << "}" + << std::endl; + }); + + // Defer operations until end of defer statement + std::cout << "Defer begin" << std::endl; + ecs.defer([&]{ + ecs.entity().set<Position>({10, 20}); + ecs.entity().set<Position>({20, 30}); + ecs.entity().set<Position>({30, 40}); + + // After the function exits, the deferred operations will be processed + // and the OnSet system will be called. + std::cout << "Operations enqueued" << std::endl; + }); + std::cout << "Defer end" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/48_is_alive/include/is_alive.h b/fggl/ecs2/flecs/examples/cpp/48_is_alive/include/is_alive.h new file mode 100644 index 0000000000000000000000000000000000000000..606a1a75d3c52280f29114710f865062b528c647 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/48_is_alive/include/is_alive.h @@ -0,0 +1,16 @@ +#ifndef IS_ALIVE_H +#define IS_ALIVE_H + +/* This generated file contains includes for project dependencies */ +#include "is_alive/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/48_is_alive/include/is_alive/bake_config.h b/fggl/ecs2/flecs/examples/cpp/48_is_alive/include/is_alive/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a53a3a1559e93b2ccd932112aefcd38c2787e9a2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/48_is_alive/include/is_alive/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 IS_ALIVE_BAKE_CONFIG_H +#define IS_ALIVE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/48_is_alive/project.json b/fggl/ecs2/flecs/examples/cpp/48_is_alive/project.json new file mode 100644 index 0000000000000000000000000000000000000000..6af431b37db554deeb4f5c2364decc24adb4e5d5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/48_is_alive/project.json @@ -0,0 +1,13 @@ +{ + "id": "is_alive", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/48_is_alive/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/48_is_alive/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d1012dc6ed8c97b6b674852762bfdbd58d394569 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/48_is_alive/src/main.cpp @@ -0,0 +1,21 @@ +#include <is_alive.h> +#include <iostream> + +int main() { + flecs::world ecs; + + // Create new entity, will be alive + auto e1 = ecs.entity(); + std::cout << "e1 alive: " << e1.is_alive() << std::endl; + + // Entity will not be alive after deleting + e1.destruct(); + std::cout << "e1 alive: " << e1.is_alive() << std::endl; + + // Create new entity, will return same id but new generation + auto e2 = ecs.entity(); + + // e2 is alive, but e1 is not because the generation increased + std::cout << "e1 alive: " << e1.is_alive() << std::endl; + std::cout << "e2 alive: " << e2.is_alive() << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/49_singleton/include/singleton.h b/fggl/ecs2/flecs/examples/cpp/49_singleton/include/singleton.h new file mode 100644 index 0000000000000000000000000000000000000000..9c7ac0a951dd7440ac92d24a24e3a0688bd877ab --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/49_singleton/include/singleton.h @@ -0,0 +1,16 @@ +#ifndef SINGLETON_H +#define SINGLETON_H + +/* This generated file contains includes for project dependencies */ +#include "singleton/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/49_singleton/include/singleton/bake_config.h b/fggl/ecs2/flecs/examples/cpp/49_singleton/include/singleton/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4d158e277e3873a305a732970d05d186e38c5246 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/49_singleton/include/singleton/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SINGLETON_BAKE_CONFIG_H +#define SINGLETON_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/49_singleton/project.json b/fggl/ecs2/flecs/examples/cpp/49_singleton/project.json new file mode 100644 index 0000000000000000000000000000000000000000..b21e6606f6c4d0d79585b723638c650ff3efbe07 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/49_singleton/project.json @@ -0,0 +1,12 @@ +{ + "id": "singleton", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/49_singleton/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/49_singleton/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..31151574ae5448d03909593b0a5030883c133167 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/49_singleton/src/main.cpp @@ -0,0 +1,49 @@ +#include <singleton.h> +#include <iostream> + +struct Game { + int score; +}; + +struct Position { + double x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + // Singleton components can simply be set on the world + ecs.set<Game>({ 10 }); + + // Similarly, they can be retrieved from the world as well + const Game *g = ecs.get<Game>(); + std::cout << "Score: " << g->score << std::endl; + + // Systems can request a singleton components. Note that we need to mark the + // component as InOut, as any component that is not stored on the entity + // itself is readonly by default. + ecs.system<Position>() + .term<Game>().inout(flecs::InOut).singleton() + .iter([](flecs::iter it, Position* p) { + // The singleton component can be retrieved as a regular column + auto game = it.term<Game>(2); // 2, because Position is 1 + + for (auto i : it) { + if (p[i].x > 1) { + // Use as pointer, since it's a single value + game->score ++; + } + } + }); + + // Create a few dummy entities + ecs.entity().set<Position>({0, 1}); + ecs.entity().set<Position>({1, 2}); + ecs.entity().set<Position>({2, 3}); + + // Run system + ecs.progress(); + + // Print score again (should be 1) + std::cout << "Score: " << g->score << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/50_disable_component/include/disable_component.h b/fggl/ecs2/flecs/examples/cpp/50_disable_component/include/disable_component.h new file mode 100644 index 0000000000000000000000000000000000000000..c58de65171fe551cfc766ea05b270e74be1c4fe6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/50_disable_component/include/disable_component.h @@ -0,0 +1,16 @@ +#ifndef DISABLE_COMPONENT_H +#define DISABLE_COMPONENT_H + +/* This generated file contains includes for project dependencies */ +#include "disable_component/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/50_disable_component/include/disable_component/bake_config.h b/fggl/ecs2/flecs/examples/cpp/50_disable_component/include/disable_component/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b73eb55911342a17192fd0156865a35a9f43bcb9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/50_disable_component/include/disable_component/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DISABLE_COMPONENT_BAKE_CONFIG_H +#define DISABLE_COMPONENT_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/50_disable_component/project.json b/fggl/ecs2/flecs/examples/cpp/50_disable_component/project.json new file mode 100644 index 0000000000000000000000000000000000000000..e1bd055591accf25a9b5ef49fb0b3b9469ac0c07 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/50_disable_component/project.json @@ -0,0 +1,13 @@ +{ + "id": "disable_component", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/50_disable_component/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/50_disable_component/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aab46a610c80b10840eb14f838c81dd790713c1e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/50_disable_component/src/main.cpp @@ -0,0 +1,54 @@ +#include <disable_component.h> +#include <iostream> + +struct Position { + float x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + auto e1 = ecs.entity("e1") + .set<Position>({10, 20}); + + auto e2 = ecs.entity("e2") + .set<Position>({30, 40}); + + auto e3 = ecs.entity("e3") + .set<Position>({50, 60}); + + // All components will show up as enabled + std::cout << "e1 enabled: " << e1.is_enabled<Position>() << std::endl; + std::cout << "e2 enabled: " << e2.is_enabled<Position>() << std::endl; + std::cout << "e3 enabled: " << e3.is_enabled<Position>() << std::endl; + + // Create system that matches all entities with position + auto s = ecs.system<const Position>() + .each([](flecs::entity e, const Position& p) { + std::cout << e.name() << " = {" << p.x << ", " << p.y << "}" << std::endl; + }); + + std::cout << std::endl << "1st run: all components enabled" << std::endl; + s.run(); + + // Enable component of entity 1, disable component of entity 2 + e1.enable<Position>(); + e2.disable<Position>(); + + // e2 will now show up as disabled + std::cout << std::endl; + std::cout << "e1 enabled: " << e1.is_enabled<Position>() << std::endl; + std::cout << "e2 enabled: " << e2.is_enabled<Position>() << std::endl; + std::cout << "e3 enabled: " << e3.is_enabled<Position>() << std::endl; + + std::cout << std::endl << "2nd run: e2 is disabled" << std::endl; + s.run(); + + // Print types. Both e1 and e2 will have DISABLED|Position. This does not + // actually mean that both are disabled. Instead it means that both entities + // have a bitset that tracks whether the component is disabled. + std::cout << std::endl; + std::cout << "e1 type: " << e1.type().str() << std::endl; + std::cout << "e2 type: " << e2.type().str() << std::endl; + std::cout << "e3 type: " << e3.type().str() << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..2db0b4fdf55be6d9631ace1d27b70d12c9616abd --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/include/dynamic_buffer.h @@ -0,0 +1,16 @@ +#ifndef DYNAMIC_BUFFER_H +#define DYNAMIC_BUFFER_H + +/* This generated file contains includes for project dependencies */ +#include "dynamic_buffer/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..2316e49c5f13c18327e7fb8f6b25b2869df099b7 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/include/dynamic_buffer/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 DYNAMIC_BUFFER_BAKE_CONFIG_H +#define DYNAMIC_BUFFER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/project.json b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/project.json new file mode 100644 index 0000000000000000000000000000000000000000..4483612e9bee78c7a2a8a8955641a1a3a9eeafb5 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/project.json @@ -0,0 +1,13 @@ +{ + "id": "dynamic_buffer", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e76cb2d2beb4268ca4004fcf4d8aec671187982e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/53_dynamic_buffer/src/main.cpp @@ -0,0 +1,123 @@ +#include <dynamic_buffer.h> +#include <iostream> + +struct Position { + float x, y; +}; + +/* Non-POD type. Don't use std::vector on purpose to demonstrate how a type + * can register its own lifecycle actions. */ +struct DynamicBuffer { + DynamicBuffer() : data(nullptr), count(0) { + std::cout << "DynamicBuffer::ctor" << std::endl; + } + + ~DynamicBuffer() { + std::cout << "DynamicBuffer::dtor" << std::endl; + free(data); + } + + DynamicBuffer(const DynamicBuffer& obj) { + *this = obj; + } + + DynamicBuffer(DynamicBuffer&& obj) { + *this = std::move(obj); + } + + DynamicBuffer& operator=(const DynamicBuffer& src) { + std::cout << "DynamicBuffer::copy" << std::endl; + if (data) { + free(data); + } + + size_t size = sizeof(int) * src.count; + data = static_cast<int*>(malloc(size)); + count = src.count; + memcpy(data, src.data, size); + + return *this; + } + + DynamicBuffer& operator=(DynamicBuffer&& src) { + std::cout << "DynamicBuffer::move" << std::endl; + if (data) { + free(data); + } + + data = src.data; + count = src.count; + + src.data = nullptr; + src.count = 0; + + return *this; + } + + int *data; + size_t count; +}; + +/* Add an element to a new or existing buffer */ +void add_elem(flecs::entity e, int value) { + DynamicBuffer *ptr = e.get_mut<DynamicBuffer>(); + + ptr->count ++; + ptr->data = static_cast<int*>(realloc(ptr->data, ptr->count * sizeof(int))); + ptr->data[ptr->count - 1] = value; +} + +/* Remove element from buffer */ +void remove_elem(flecs::entity e, size_t elem) { + DynamicBuffer *ptr = e.get_mut<DynamicBuffer>(); + + size_t last = ptr->count - 1; + + if (last >= elem) { + if (last - elem) { + ptr->data[elem] = ptr->data[last]; + } + + ptr->count --; + } +} + +/* Get element from a buffer */ +int* get_elem(flecs::entity e, size_t elem) { + const DynamicBuffer *ptr = e.get<DynamicBuffer>(); + + if (ptr && (ptr->count > elem)) { + return &ptr->data[elem]; + } else { + return NULL; + } +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + auto e = ecs.entity(); + + /* Add 3 elements to the buffer. The first add will add the DynamicBuffer + * element to the entity. */ + add_elem(e, 10); + add_elem(e, 20); + add_elem(e, 30); + + std::cout << "Elem 1 = " << *get_elem(e, 1) << std::endl; + + /* Remove element. This will move the last element from the buffer to the + * removed element. */ + remove_elem(e, 1); + + std::cout << "Elem 1 = " << *get_elem(e, 1) << " (after remove)" << std::endl; + + /* Add component. This causes the entity to move between tables, and will + * invoke DynamicComponent::move to copy the component value from the src to + * the dst table. This also invokes DynamicComponent::ctor to construct the + * component in the dst table, and DynamicComponent::dtor to destruct the + * element in the src table. */ + e.add<Position>(); + + /* World gets cleaned up, which invokes DynamicComponent::dtor. */ +} diff --git a/fggl/ecs2/flecs/examples/cpp/54_simple_threads/include/simple_threads.h b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/include/simple_threads.h new file mode 100644 index 0000000000000000000000000000000000000000..bd86351797970f469f5589aaa2a59ef5b36f9d8a --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/include/simple_threads.h @@ -0,0 +1,16 @@ +#ifndef SIMPLE_THREADS_H +#define SIMPLE_THREADS_H + +/* This generated file contains includes for project dependencies */ +#include "simple_threads/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/54_simple_threads/include/simple_threads/bake_config.h b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/include/simple_threads/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..9abe0eef75fc55644fc1785aecb6d88bcf9e6f10 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/include/simple_threads/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 SIMPLE_THREADS_BAKE_CONFIG_H +#define SIMPLE_THREADS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/54_simple_threads/project.json b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/project.json new file mode 100644 index 0000000000000000000000000000000000000000..7516422be13a3d335b406024977441e04eb77bfa --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/project.json @@ -0,0 +1,13 @@ +{ + "id": "simple_threads", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/54_simple_threads/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79b21f330ef865528a36cd207f4697cbc84d3b1f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/54_simple_threads/src/main.cpp @@ -0,0 +1,55 @@ +#include <simple_threads.h> +#include <iostream> + +/* Component types */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int argc, char *argv[]) { + // Flecs does not have an operating system abstraction for threading. To use + // threading, an application must first provide threading functions by + // setting the appropriate functions in the OS API. See the examples in the + // os_api folder for how this can be achieved. + // + // To run the example, add the posix OS API example as a dependency, and + // uncomment this line. Without doing this, the example will not run. */ + + // posix_set_os_api(); + + flecs::world ecs(argc, argv); + + ecs.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + + // Print the id of the current stage. This allows us to see which + // entities are processed by which thread. + std::cout << "Stage " << e.world().get_stage_id() << ": " + << e.id() << std::endl; + }); + + // Create a bunch of entities + for (int i = 0; i < 10; i ++) { + ecs.entity() + .set<Position>({0, 0}) + .set<Velocity>({1, 1}); + } + + // Set target FPS for main loop to 1 frame per second + ecs.set_target_fps(1); + + // Set number of threads to 2. This will run all systems on all threads, and + // divide entities equally between the systems. + ecs.set_threads(2); + + // Run systems. + while (ecs.progress()) { } + + return 0; +} diff --git a/fggl/ecs2/flecs/examples/cpp/55_custom_threads/include/custom_threads.h b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/include/custom_threads.h new file mode 100644 index 0000000000000000000000000000000000000000..76798630e552c2121056d1136199106b3eda714e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/include/custom_threads.h @@ -0,0 +1,16 @@ +#ifndef CUSTOM_THREADS_H +#define CUSTOM_THREADS_H + +/* This generated file contains includes for project dependencies */ +#include "custom_threads/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/55_custom_threads/include/custom_threads/bake_config.h b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/include/custom_threads/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..60c9a8600634f6a4ab75c1002fa2983394f78845 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/include/custom_threads/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CUSTOM_THREADS_BAKE_CONFIG_H +#define CUSTOM_THREADS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/55_custom_threads/project.json b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/project.json new file mode 100644 index 0000000000000000000000000000000000000000..be6edf78da62a50a2289b83d6e02e2de275f899a --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/project.json @@ -0,0 +1,13 @@ +{ + "id": "custom_threads", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/55_custom_threads/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ea8a3268578ef729fd3b3b261c0354ac2ff59ba6 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/55_custom_threads/src/main.cpp @@ -0,0 +1,172 @@ +#include <custom_threads.h> +#include <iostream> + +// Component types +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +// Type passed as thread arg +struct thread_ctx_t{ + flecs::world& stage; + flecs::system<Position, const Velocity> system; + flecs::query<Position, const Velocity> query; +}; + +// Dummy thread function +void* thread_func(void *arg) { + thread_ctx_t *ctx = static_cast<thread_ctx_t*>(arg); + auto& ecs = ctx->stage; + auto system = ctx->system; + auto query = ctx->query; + + int32_t stage_id = ecs.get_stage_id(); + int32_t stage_count = ecs.get_stage_count(); + + // The worker thread's main loop would start here, but because this is an + // example without threads and we call the thread explicitly from the main + // loop don't actually loop + // while (!ecs_should_quit(world)) { + + // This is where the thread would wait for the mainthread to signal that + // it is safe to start processing. + // wait_for_staging_begin() + + // Run system with the ecs_run_worker function. This will automatically + // subdivide entities evenly across threads. If the regular ecs_run + // function is used the system will evaluate all matching entities. + system.run_worker(stage_id, stage_count).stage(ecs); + + // Evaluate query with the ecs_query_next_worker function. This will, + // just as with the system, automatically subdivide entities across + // threads. When the regular ecs_query_next function is used, all + // entities will be evaluated. + query.each_worker(stage_id, stage_count, + [](flecs::entity, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + // So far, neither the system nor the query enqueued any operations. + // Enqueueing commands is done simply by calling the regular API with + // the provided staging context. The command will be executed when the + // stage is merged. + + /* The following operations will be enqueued */ + auto e = ecs.entity() + .set<Position>({10, 20}); + + Velocity *v = e.get_mut<Velocity>(); + v->x = 1; + v->y = 2; + e.modified<Velocity>(); + + // This is be where the thread would signal to the main thread that it + // is done processing. The main thread will wait for all threads to + // finish, after which the merge will take place. + // signal_main() + // } + + return NULL; +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Create both a system and a query so both can be demonstrated + auto move_system = ecs.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + std::cout << "Stage " << e.world().get_stage_id() << ": " + << e.id() << std::endl; + }); + + auto q = ecs.query<Position, const Velocity>(); + + // Create a bunch of entities + for (int i = 0; i < 5; i ++) { + ecs.entity() + .set<Position>({0, 0}) + .set<Velocity>({1, 1}); + } + + // Create two stages. This lets two threads concurrently mutate the world + ecs.set_stages(2); + + // Get the stage objects. These are passed to the threads and should be + // provided to API calls instead of the world. The stage objects provide a + // transparent mechanism for passing a thread context to regular flecs API + // functions without requiring a dedicated API for enqueuing commands. + flecs::world stage_1 = ecs.get_stage(0); + flecs::world stage_2 = ecs.get_stage(1); + + thread_ctx_t ctx_1 = { stage_1, move_system, q }; + thread_ctx_t ctx_2 = { stage_2, move_system, q }; + + // Start threads (replace this with an OS thread create function) + // thread_new(thread_func, ctx_1); + // thread_new(thread_func, ctx_2); + (void)ctx_1; (void)ctx_2; + + // Set target FPS for main loop to 1 frame per second + ecs.set_target_fps(1); + + // Main loop. Instead of ecs_progress, use these functions to control when a + // frame starts/stops, and when merging should occur. + + while ( !ecs.should_quit()) { + std::cout << std::endl << "Frame begin" << std::endl; + + // Start frame. This does time measurements, and if an application has + // set a target FPS, this function will sleep for as long as necessary + // to ensure the application does not exceed the set FPS. The number + // passed to the function is delta_time, which should be set to the + // same value as what would ordinarly be passed to ecs_progress + ecs.frame_begin(); + + // Start staging. After this call it is safe for threads to start adding + // commands to the queue. This also flags the moment at which it is no + // longer valid to do mutations on the regular world object. When an + // application would attempt to, for example, do + // + // "ecs_add(world, e, Position)" after staging_begin, an assert would be + // thrown. It is still safe to call functions that do not mutate the + // world, such as "ecs_has". + // + // If the world were mutated while in staged mode by accident (say, + // because asserts are not enabled) threads that are asynchronously + // accessing the world may run into undefined behavior as a result of + // data races. + ecs.staging_begin(); + + // This is where the main thread would signal the threads to start + // processing (and enqueueing commands) */ + // signal_threads(); + + // Stub running the threads + thread_func(&ctx_1); + thread_func(&ctx_2); + + // This is where the main thread would wait for the threads to finish + // processing + // wait_for_threads(); + + // End staging. This will automatically merge data from all threads. + // Applications will be able to disable automatic merging, and even take + // control over which stages are merged and which stages aren't. It is + // valid to begin/end staging multiple times per frame. This is commonly + // required when an application has multiple sync points. + // + // After staging_end() writing to a stage is no longer allowed until the + // next staging_begin(). Doing so may trigger an assert. + ecs.staging_end(); + + // Finish frame. This executes post-frame actions + ecs.frame_end(); + } +} diff --git a/fggl/ecs2/flecs/examples/cpp/56_custom_merge/include/custom_merge.h b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/include/custom_merge.h new file mode 100644 index 0000000000000000000000000000000000000000..785581880ca2221221f5db96cc43b927ed2c0c8e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/include/custom_merge.h @@ -0,0 +1,16 @@ +#ifndef CUSTOM_MERGE_H +#define CUSTOM_MERGE_H + +/* This generated file contains includes for project dependencies */ +#include "custom_merge/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/56_custom_merge/include/custom_merge/bake_config.h b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/include/custom_merge/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..dc8438c7fec7921958dd1ca128a868862008bb01 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/include/custom_merge/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CUSTOM_MERGE_BAKE_CONFIG_H +#define CUSTOM_MERGE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/56_custom_merge/project.json b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/project.json new file mode 100644 index 0000000000000000000000000000000000000000..a261f0331782abf3034dd71eebaa310583b80d50 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/project.json @@ -0,0 +1,13 @@ +{ + "id": "custom_merge", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/56_custom_merge/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b7f6eb044b9920b84e3fdcab715bf5169cf18f7d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/56_custom_merge/src/main.cpp @@ -0,0 +1,55 @@ +#include <custom_merge.h> +#include <iostream> + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Create three stages. + ecs.set_stages(3); + + // By default stages are merged automatically when calling staging_end(). + // This behavior can be overridden so that an application can control when + // each stage is merged, and in what order. + // + // The set_automerge() function enables or disables automatic merging for + // all current stages in the world, and sets the default for new stages. + ecs.set_automerge(false); + + flecs::world stage_1 = ecs.get_stage(0); + flecs::world stage_2 = ecs.get_stage(1); + flecs::world stage_3 = ecs.get_stage(2); + + // Enable automerging just for stage 1 + stage_1.set_automerge(true); + + // Begin staging. Note that this also demonstrates a very minimal example of + // staging, without frame_begin() and frame_end() functions. Also note that + // we need to enable staging before we may write to a stage. + ecs.staging_begin(); + + // Create an entity in each stage + auto e1 = stage_1.entity("e_stage_1"); + auto e2 = stage_2.entity("e_stage_2"); + auto e3 = stage_3.entity("e_stage_3"); + + // End staging. This will only merge the stages for which automerging has + // been enabled, which in this case is just stage_1. + ecs.staging_end(); + + // Show that only e1 has a name + std::cout << "e1: \"" << e1.name() << "\", " + << "e2: \"" << e2.name() << "\", " + << "e3: \"" << e3.name() << "\"" << std::endl; + + // Now merge the remaining stages. We could either call merge() on the world + // which merges all stages, or we could merge each individual stage. The + // latter gives us more control over the order in which stages are merged. + // Note that merging is only allowed when staging is disabled. + stage_3.merge(); + stage_2.merge(); + + // All entities are now merged + std::cout << "e1: \"" << e1.name() << "\", " + << "e2: \"" << e2.name() << "\", " + << "e3: \"" << e3.name() << "\"" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/include/inspect_entity.h b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/include/inspect_entity.h new file mode 100644 index 0000000000000000000000000000000000000000..e824d5f8e325e0e7438a9743c776ec999575696d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/include/inspect_entity.h @@ -0,0 +1,16 @@ +#ifndef INSPECT_ENTITY_H +#define INSPECT_ENTITY_H + +/* This generated file contains includes for project dependencies */ +#include "inspect_entity/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/include/inspect_entity/bake_config.h b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/include/inspect_entity/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..ac5d16e54565a3808af913abd39e3f4862d20333 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/include/inspect_entity/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 INSPECT_ENTITY_BAKE_CONFIG_H +#define INSPECT_ENTITY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/project.json b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/project.json new file mode 100644 index 0000000000000000000000000000000000000000..cc19fcd010e126f260c64210a8619468e5ee7b31 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/project.json @@ -0,0 +1,13 @@ +{ + "id": "inspect_entity", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7773723c0302a96d38da5aedffb49cc8a8023c9f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/57_inspect_entity/src/main.cpp @@ -0,0 +1,134 @@ +#include <inspect_entity.h> +#include <iostream> + +using std::cout; +using std::endl; +using std::string; +using flecs::entity; +using flecs::IsA; + +/* Components */ +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +struct Attack { + double value; +}; + +struct Defense { + double value; +}; + +struct MaxVelocity { + double value; +}; + +// Tags +struct CanFly { }; +struct CanFight { }; +struct CanFire { }; + +// Ship states +struct ShipState { + struct Idle { }; + struct Flying { }; + struct Fighting { }; +}; + +string indent(std::uint32_t lvl) { + return string(lvl * 2u, ' '); +} + +// Visit all components +void visit_components(const entity& e, std::uint32_t lvl = 0) { + // Iterate all components of entity + e.each([&](flecs::id& id) { + // Skip IsA relations + if (id.has_relation(IsA)) { + return; + } + + cout << indent(lvl) << " - "; + + // Print role, if id has one + if (id.has_role()) { + cout << id.role_str() << " | "; + } + + // Print relation, if id has one + if (id.is_pair()) { + cout << "(" << id.relation().name() << id.object().name() << ")"; + } else { + cout << id.object().name(); + } + + cout << endl; + }); +} + +// Recursively visit IsA relationships +void visit_is_a(const entity& e, std::uint32_t lvl = 0) { + // Iterate all IsA relationships + e.each(IsA, [&](const entity& object) { + cout << indent(lvl) << "(IsA, " << object.name() << ")" << endl; + visit_is_a(object, lvl + 1); + visit_components(object, lvl + 1); + }); +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // SpaceShip state machine + auto ShipStateType = ecs.type().component<ShipState>() + .add<ShipState::Idle>() + .add<ShipState::Flying>() + .add<ShipState::Fighting>(); + + // Base SpaceShip + auto SpaceShip = ecs.prefab("SpaceShip") + .set<MaxVelocity>({100}) + .set_owned<Position>({0, 0}) + .set_owned<Velocity>({0, 0}) + .add<CanFly>() + .add_switch(ShipStateType) + .add_case<ShipState::Idle>(); + + // Frigate + auto Frigate = ecs.prefab("Frigate") + .add(IsA, SpaceShip) + .set<Attack>({100}) + .set<Defense>({75}) + .set<MaxVelocity>({150}) + .add<CanFight>(); + + // Heavy Frigate + auto HeavyFrigate = ecs.prefab("HeavyFrigate") + .add(IsA, Frigate) + .set<Attack>({150}); + + // Frigate instance + auto Rocinante = ecs.entity("Roci") + .add(IsA, HeavyFrigate) + .set<Position>({10, 20}) + .set<Velocity>({1, 1}); + + + // Start printing contents of Rocinante + cout << endl; + cout << Rocinante.name() << ": " << endl; + + // First, visit the IsA relationships to print the inheritance tree + visit_is_a(Rocinante); + + // Print components of Rocinante itself + cout << endl; + cout << "Own components: " << endl; + visit_components(Rocinante); + cout << endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/58_entity_view/include/entity_view.h b/fggl/ecs2/flecs/examples/cpp/58_entity_view/include/entity_view.h new file mode 100644 index 0000000000000000000000000000000000000000..f82736e7f00a70d4e2630cff8ebc7381254c113d --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/58_entity_view/include/entity_view.h @@ -0,0 +1,16 @@ +#ifndef ENTITY_VIEW_H +#define ENTITY_VIEW_H + +/* This generated file contains includes for project dependencies */ +#include "entity_view/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/58_entity_view/include/entity_view/bake_config.h b/fggl/ecs2/flecs/examples/cpp/58_entity_view/include/entity_view/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..08a61b2285ad65bce800172444f328e7bea68042 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/58_entity_view/include/entity_view/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 ENTITY_VIEW_BAKE_CONFIG_H +#define ENTITY_VIEW_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/58_entity_view/project.json b/fggl/ecs2/flecs/examples/cpp/58_entity_view/project.json new file mode 100644 index 0000000000000000000000000000000000000000..bad27adb202433ef36526bf48863028d163f1d2e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/58_entity_view/project.json @@ -0,0 +1,13 @@ +{ + "id": "entity_view", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/58_entity_view/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/58_entity_view/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e771a726839f4ffa134d96b7b6b58e64f69cb344 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/58_entity_view/src/main.cpp @@ -0,0 +1,48 @@ +#include <entity_view.h> +#include <iostream> + +struct Position { + double x, y; +}; + +// Component with entity reference. Use entity_view to provide compiler-checked +// protection against calling functions that accidentally mutate an entity. +struct EntityView { + flecs::entity_view ev; +}; + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + ecs.system<EntityView>() + .each([](flecs::entity e, EntityView& v) { + // Operations that don't mutate the entity are allowed + std::cout << "Readonly handle: " << v.ev.name() << std::endl; + + // Uncommenting this line will fail, as it is not possible to mutate + // entity_view instances + // v.ev.set<Position>({10, 20}); + + // To mutate the entity, use the mut function. This will create a + // new handle for the current stage (obtained from the entity) which + // lets us do deferred operations + v.ev.mut(e).set<Position>({10, 20}); + + // The mut function also accepts an iterator (for iter functions) + // and a world/stage. + }); + + // Create regular (mutable) entity to store in EntityView component + auto Foo = ecs.entity("Foo"); + + // Create entity with EntityView component to match with system. Store the + // Foo entity handle in the component as a readonly handle. + ecs.entity("Bar").set<EntityView>({ Foo }); + + // Run the system + ecs.progress(); + + // Verify that Position has been added to Foo + const Position *p = Foo.get<Position>(); + std::cout << "Foo position: " << p->x << ", " << p->y << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/cpp/59_pair_types/include/pair_types.h b/fggl/ecs2/flecs/examples/cpp/59_pair_types/include/pair_types.h new file mode 100644 index 0000000000000000000000000000000000000000..d02e44cf3e0f2fe1248eecccfc105ef1e2af53ff --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/59_pair_types/include/pair_types.h @@ -0,0 +1,16 @@ +#ifndef PAIR_TYPES_H +#define PAIR_TYPES_H + +/* This generated file contains includes for project dependencies */ +#include "pair_types/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/59_pair_types/include/pair_types/bake_config.h b/fggl/ecs2/flecs/examples/cpp/59_pair_types/include/pair_types/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..54ec90f5575f583f1fe576ae446055dd2755c09f --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/59_pair_types/include/pair_types/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 PAIR_TYPES_BAKE_CONFIG_H +#define PAIR_TYPES_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/59_pair_types/project.json b/fggl/ecs2/flecs/examples/cpp/59_pair_types/project.json new file mode 100644 index 0000000000000000000000000000000000000000..80d49c983031f527476273fe93f5f4e95a54dcfa --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/59_pair_types/project.json @@ -0,0 +1,12 @@ +{ + "id": "pair_types", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/59_pair_types/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/59_pair_types/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99332d0fda087f760a5dd4f10d9831cf6d6facfa --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/59_pair_types/src/main.cpp @@ -0,0 +1,56 @@ +#include <pair_types.h> +#include <iostream> + +// The relation type +struct Eats { int amount; }; + +// The object types +struct Apples { }; +struct Pears { }; + +int main(int, char*[]) { + flecs::world ecs; + + // Pair types are a feature that allow an application to define a relation- + // object pair at compile time. Pair types can be used in any location where + // the API expects a component type. + // + // When setting or getting a value with a pair type, the value assumes the + // type in the pair that is not empty. If both of the types in the pair are + // not empty, the pair assumes the type of the relation. + using EatsApples = flecs::pair<Eats, Apples>; + using EatsPears = flecs::pair<Eats, Pears>; + + // Query for entities with both (Eats, Apples) and (Eats, Pears) + auto q = ecs.query<EatsApples, EatsPears>(); + + // Pair types can be passed to function-style get/set's. Note that the pair + // type must be passed by value, and that we need to use the -> operator + // to access the value of the pair. + auto e = ecs.entity() + .set([](EatsApples a, EatsPears p) { + a->amount = 10; + p->amount = 20; + }); + + // Using a pair type is equivalent to specifying the individual types + e.set<Eats, Apples>({10}); + e.set<EatsApples>({10}); + + // The each function accepts pair types. Note that just as with the function + // style get/set, the pair is passed by value and the -> operator is used. + q.each([](EatsApples a, EatsPears p) { + std::cout << "Apples: " << a->amount + << ", Pears: " << p->amount << std::endl; + }); + + // When using the iter function, the actual type of the pair must be used. + // This type can be either provided directly or indirectly, by providing the + // pair_type_t trait. + q.iter([](flecs::iter it, Eats *a, flecs::pair_type_t<EatsPears> *p) { + for (auto i : it) { + std::cout << "Apples: " << a[i].amount + << ", Pears: " << p[i].amount << std::endl; + } + }); +} diff --git a/fggl/ecs2/flecs/examples/cpp/60_observer/include/observer.h b/fggl/ecs2/flecs/examples/cpp/60_observer/include/observer.h new file mode 100644 index 0000000000000000000000000000000000000000..0a29e4b82340f26843f880cb29c00f8b8b0ef43e --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/60_observer/include/observer.h @@ -0,0 +1,16 @@ +#ifndef OBSERVER_H +#define OBSERVER_H + +/* This generated file contains includes for project dependencies */ +#include "observer/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/60_observer/include/observer/bake_config.h b/fggl/ecs2/flecs/examples/cpp/60_observer/include/observer/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..b1d7efa313d2e0c4cc0e2ad21bd65e6a37ee3266 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/60_observer/include/observer/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 OBSERVER_BAKE_CONFIG_H +#define OBSERVER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/60_observer/project.json b/fggl/ecs2/flecs/examples/cpp/60_observer/project.json new file mode 100644 index 0000000000000000000000000000000000000000..60bae958a047a362a1679d30ba3a560b9f668a65 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/60_observer/project.json @@ -0,0 +1,12 @@ +{ + "id": "observer", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/60_observer/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/60_observer/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..35b2918c81b0dd0fb150605425999f0d10674bf3 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/60_observer/src/main.cpp @@ -0,0 +1,35 @@ +#include <observer.h> +#include <iostream> + +struct Position { + float x, y; +}; + +struct Velocity { + float x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Apples = ecs.entity(); + + // An observer triggers when an event matching one of its terms matches, and + // the entity matches with all other terms. + ecs.observer<Position, Velocity>() + .event(flecs::OnAdd) + .term(Likes, flecs::Wildcard) + .each([&](Position&, Velocity&) { + std::cout << "Observer triggered!" << std::endl; + }); + + auto e = ecs.entity(); + + e.add<Position>(); // Observer does not trigger + + e.add<Velocity>(); // Observer does not trigger + + // Observer triggers, entity matches all of its terms + e.add(Likes, Apples); +} diff --git a/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/include/custom_pipeline.h b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/include/custom_pipeline.h new file mode 100644 index 0000000000000000000000000000000000000000..ea8b94d4d0d8f83289f186d7fff26a1437ddaa5b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/include/custom_pipeline.h @@ -0,0 +1,16 @@ +#ifndef CUSTOM_PIPELINE_H +#define CUSTOM_PIPELINE_H + +/* This generated file contains includes for project dependencies */ +#include "custom_pipeline/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/include/custom_pipeline/bake_config.h b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/include/custom_pipeline/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..8b3d68a78b08b70712e3036ab7738fd1e4cca89b --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/include/custom_pipeline/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CUSTOM_PIPELINE_BAKE_CONFIG_H +#define CUSTOM_PIPELINE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +#endif + diff --git a/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/project.json b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/project.json new file mode 100644 index 0000000000000000000000000000000000000000..fc684a7ed7f67ce106dfe0ea2df9a2ea1f0f4813 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/project.json @@ -0,0 +1,13 @@ +{ + "id": "custom_pipeline", + "type": "application", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "public": false, + "use": [ + "flecs" + ], + "language": "c++" + } +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/src/main.cpp b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2204de507be961aa27c494880b801c221eb97cc4 --- /dev/null +++ b/fggl/ecs2/flecs/examples/cpp/61_custom_pipeline/src/main.cpp @@ -0,0 +1,46 @@ +#include <custom_pipeline.h> +#include <iostream> + +int main(int, char *[]) { + flecs::world ecs; + + // Create three custom phases for the pipeline + auto PreFrame = ecs.entity(); + auto OnFrame = ecs.entity(); + auto PostFrame = ecs.entity(); + + // Create the pipeline with the three custom phases + flecs::pipeline pip = ecs.pipeline("MyPipeline") + .add(PreFrame) + .add(OnFrame) + .add(PostFrame); + + // Make the world use the custom pipeline. After this the pipeline should + // no longer be modified. + ecs.set_pipeline(pip); + + // Define a system for each of the phases. Define them in opposite order so + // we can see the pipeline orders them correctly. + ecs.system<>() + .kind(PostFrame) + .iter([&](flecs::iter) { + std::cout << "- PostFrame" << std::endl; + }); + + ecs.system<>() + .kind(OnFrame) + .iter([&](flecs::iter) { + std::cout << "- OnFrame" << std::endl; + }); + + ecs.system<>() + .kind(PreFrame) + .iter([&](flecs::iter) { + std::cout << "- PreFrame" << std::endl; + }); + + // Run the pipeline once + std::cout << "Begin" << std::endl; + ecs.progress(); + std::cout << "End" << std::endl; +} diff --git a/fggl/ecs2/flecs/examples/os_api/bake/include/flecs-os_api-bake/bake_config.h b/fggl/ecs2/flecs/examples/os_api/bake/include/flecs-os_api-bake/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..319bfa74668ee79b4185e2c05a85dc0ae6a2aa38 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/bake/include/flecs-os_api-bake/bake_config.h @@ -0,0 +1,42 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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_OS_API_BAKE_BAKE_CONFIG_H +#define FLECS_OS_API_BAKE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> +#ifdef __BAKE__ +#include <bake_util.h> +#endif + +/* Convenience macro for exporting symbols */ +#ifndef flecs_os_api_bake_STATIC +#if flecs_os_api_bake_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_OS_API_BAKE_API __declspec(dllexport) +#elif flecs_os_api_bake_EXPORTS + #define FLECS_OS_API_BAKE_API __attribute__((__visibility__("default"))) +#elif defined _MSC_VER + #define FLECS_OS_API_BAKE_API __declspec(dllimport) +#else + #define FLECS_OS_API_BAKE_API +#endif +#else + #define FLECS_OS_API_BAKE_API +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/os_api/bake/include/flecs_os_api_bake.h b/fggl/ecs2/flecs/examples/os_api/bake/include/flecs_os_api_bake.h new file mode 100644 index 0000000000000000000000000000000000000000..1cfc63c263bc7dc47fc8fdfe32e363169ff3ccc2 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/bake/include/flecs_os_api_bake.h @@ -0,0 +1,19 @@ +#ifndef FLECS_OS_API_BAKE_H +#define FLECS_OS_API_BAKE_H + +/* This generated file contains includes for project dependencies */ +#include "flecs-os_api-bake/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_OS_API_BAKE_API +void bake_set_os_api(void); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/os_api/bake/project.json b/fggl/ecs2/flecs/examples/os_api/bake/project.json new file mode 100644 index 0000000000000000000000000000000000000000..110b3b8ab6efc23690d7fb39db3623423c28e3cf --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/bake/project.json @@ -0,0 +1,12 @@ +{ + "id": "flecs.os_api.bake", + "type": "package", + "value": { + "author": "Sander Mertens", + "description": "Bake OS API implementation", + "use": [ + "flecs", + "bake.util" + ] + } +} diff --git a/fggl/ecs2/flecs/examples/os_api/bake/src/main.c b/fggl/ecs2/flecs/examples/os_api/bake/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..200f70a1f3077d3ef235f63d83daf5e519969e5c --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/bake/src/main.c @@ -0,0 +1,174 @@ +#include <flecs_os_api_bake.h> + +static +ecs_os_thread_t bake_thread_new( + ecs_os_thread_callback_t callback, + void *param) +{ + return (ecs_os_thread_t)ut_thread_new(callback, param); +} + +static +void* bake_thread_join( + ecs_os_thread_t thread) +{ + void *arg; + ut_thread_join((ut_thread)thread, &arg); + return arg; +} + +static +int32_t bake_ainc(int32_t *value) { + return ut_ainc(value); +} + +static +int32_t bake_adec(int32_t *value) { + return ut_adec(value); +} + +static +ecs_os_mutex_t bake_mutex_new(void) { + struct ut_mutex_s *m = ecs_os_malloc(sizeof(struct ut_mutex_s)); + ut_mutex_new(m); + return (ecs_os_mutex_t)(uintptr_t)m; +} + +static +void bake_mutex_free(ecs_os_mutex_t mutex) { + ut_mutex_free((struct ut_mutex_s*)mutex); + ecs_os_free((struct ut_mutex_s*)mutex); +} + +static +void bake_mutex_lock(ecs_os_mutex_t mutex) { + ut_mutex_lock((struct ut_mutex_s*)mutex); +} + +static +void bake_mutex_unlock(ecs_os_mutex_t mutex) { + ut_mutex_unlock((struct ut_mutex_s*)mutex); +} + +static +ecs_os_cond_t bake_cond_new(void) { + struct ut_cond_s *c = ecs_os_malloc(sizeof(struct ut_cond_s)); + ut_cond_new(c); + return (ecs_os_cond_t)(uintptr_t)c; +} + +static +void bake_cond_free(ecs_os_cond_t cond) { + ut_cond_free((struct ut_cond_s *)cond); + ecs_os_free((struct ut_cond_s *)cond); +} + +static +void bake_cond_signal(ecs_os_cond_t cond) { + ut_cond_signal((struct ut_cond_s *)cond); +} + +static +void bake_cond_broadcast(ecs_os_cond_t cond) { + ut_cond_broadcast((struct ut_cond_s *)cond); +} + +static +void bake_cond_wait(ecs_os_cond_t cond, ecs_os_mutex_t mutex) { + ut_cond_wait((struct ut_cond_s *)cond, (struct ut_mutex_s *)mutex); +} + +static +ecs_os_dl_t bake_dlopen( + const char *dlname) +{ + return (ecs_os_dl_t)ut_dl_open(dlname); +} + +static +void bake_dlclose( + ecs_os_dl_t dl) +{ + ut_dl_close((ut_dl)dl); +} + +static +ecs_os_proc_t bake_dlproc( + ecs_os_dl_t dl, + const char *procname) +{ + ecs_os_proc_t result = (ecs_os_proc_t)ut_dl_proc((ut_dl)dl, procname); + if (!result) { + ut_raise(); + } + return result; +} + +static +char* bake_module_to_dl( + const char *module_id) +{ + const char *result = ut_locate(module_id, NULL, UT_LOCATE_LIB); + if (result) { + return ut_strdup(result); + } else { + return NULL; + } +} + +static +char* bake_module_to_etc( + const char *module_id) +{ + const char *result = ut_locate(module_id, NULL, UT_LOCATE_ETC); + if (result) { + return ut_strdup(result); + } else { + return NULL; + } +} + +static +void bake_init(void) +{ + ut_init(NULL); + if (ut_load_init(NULL, NULL, NULL, NULL)) { + ecs_os_err("warning: failed to initialize package loader"); + } +} + +static +void bake_fini(void) +{ + ut_deinit(); +} + +void bake_set_os_api(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.init_ = bake_init; + api.fini_ = bake_fini; + api.thread_new_ = bake_thread_new; + api.thread_join_ = bake_thread_join; + api.ainc_ = bake_ainc; + api.adec_ = bake_adec; + api.mutex_new_ = bake_mutex_new; + api.mutex_free_ = bake_mutex_free; + api.mutex_lock_ = bake_mutex_lock; + api.mutex_unlock_ = bake_mutex_unlock; + api.cond_new_ = bake_cond_new; + api.cond_free_ = bake_cond_free; + api.cond_signal_ = bake_cond_signal; + api.cond_broadcast_ = bake_cond_broadcast; + api.cond_wait_ = bake_cond_wait; + api.dlopen_ = bake_dlopen; + api.dlproc_ = bake_dlproc; + api.dlclose_ = bake_dlclose; + api.module_to_dl_ = bake_module_to_dl; + api.module_to_etc_ = bake_module_to_etc; + + ecs_os_set_api(&api); +} + diff --git a/fggl/ecs2/flecs/examples/os_api/posix/include/flecs-os_api-posix/bake_config.h b/fggl/ecs2/flecs/examples/os_api/posix/include/flecs-os_api-posix/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..1ea202dd164da6dbaf533c90d9bd95689836e584 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/posix/include/flecs-os_api-posix/bake_config.h @@ -0,0 +1,39 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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_OS_API_POSIX_BAKE_CONFIG_H +#define FLECS_OS_API_POSIX_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +/* Convenience macro for exporting symbols */ +#ifndef flecs_os_api_posix_STATIC +#if flecs_os_api_posix_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_OS_API_POSIX_API __declspec(dllexport) +#elif flecs_os_api_posix_EXPORTS + #define FLECS_OS_API_POSIX_API __attribute__((__visibility__("default"))) +#elif defined _MSC_VER + #define FLECS_OS_API_POSIX_API __declspec(dllimport) +#else + #define FLECS_OS_API_POSIX_API +#endif +#else + #define FLECS_OS_API_POSIX_API +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/os_api/posix/include/flecs_os_api_posix.h b/fggl/ecs2/flecs/examples/os_api/posix/include/flecs_os_api_posix.h new file mode 100644 index 0000000000000000000000000000000000000000..db93555e4f369b9886293a0512236ff1143e45c8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/posix/include/flecs_os_api_posix.h @@ -0,0 +1,19 @@ +#ifndef FLECS_OS_API_POSIX_H +#define FLECS_OS_API_POSIX_H + +/* This generated file contains includes for project dependencies */ +#include "flecs-os_api-posix/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_OS_API_POSIX_API +void posix_set_os_api(void); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/os_api/posix/project.json b/fggl/ecs2/flecs/examples/os_api/posix/project.json new file mode 100644 index 0000000000000000000000000000000000000000..38c509b10bef90312e5fe1136cae94a08141592d --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/posix/project.json @@ -0,0 +1,16 @@ +{ + "id": "flecs.os_api.posix", + "type": "package", + "value": { + "author": "Sander Mertens", + "description": "Posix OS API implementation", + "use": [ + "flecs" + ] + }, + "lang.c": { + "${os linux}": { + "lib": ["pthread"] + } + } +} diff --git a/fggl/ecs2/flecs/examples/os_api/posix/src/main.c b/fggl/ecs2/flecs/examples/os_api/posix/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..fd80b530e849b20abf7477fe99999639b8139dda --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/posix/src/main.c @@ -0,0 +1,151 @@ +#include <flecs_os_api_posix.h> + +#include "pthread.h" + +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)); + int r; + + if ((r = pthread_create (thread, NULL, callback, arg))) { + 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 +int32_t posix_ainc(int32_t *count) { + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + /* Unsupported */ + abort(); +#endif +} + +static +int32_t posix_adec(int32_t *count) { + int value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + /* Unsupported */ + abort(); +#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(); + } +} + +void posix_set_os_api(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = posix_thread_new; + api.thread_join_ = posix_thread_join; + api.ainc_ = posix_ainc; + api.adec_ = posix_adec; + api.mutex_new_ = posix_mutex_new; + api.mutex_free_ = posix_mutex_free; + api.mutex_lock_ = posix_mutex_lock; + api.mutex_unlock_ = posix_mutex_unlock; + api.cond_new_ = posix_cond_new; + api.cond_free_ = posix_cond_free; + api.cond_signal_ = posix_cond_signal; + api.cond_broadcast_ = posix_cond_broadcast; + api.cond_wait_ = posix_cond_wait; + + ecs_os_set_api(&api); +} + diff --git a/fggl/ecs2/flecs/examples/os_api/stdcpp/include/flecs-os_api-stdcpp/bake_config.h b/fggl/ecs2/flecs/examples/os_api/stdcpp/include/flecs-os_api-stdcpp/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..9e40841f0e95570b708390dfd6dca025ac13dd66 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/stdcpp/include/flecs-os_api-stdcpp/bake_config.h @@ -0,0 +1,39 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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_OS_API_STDCPP_BAKE_CONFIG_H +#define FLECS_OS_API_STDCPP_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> + +/* Convenience macro for exporting symbols */ +#ifndef flecs_os_api_stdcpp_STATIC +#if flecs_os_api_stdcpp_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_OS_API_STDCPP_API __declspec(dllexport) +#elif flecs_os_api_stdcpp_EXPORTS + #define FLECS_OS_API_STDCPP_API __attribute__((__visibility__("default"))) +#elif defined _MSC_VER + #define FLECS_OS_API_STDCPP_API __declspec(dllimport) +#else + #define FLECS_OS_API_STDCPP_API +#endif +#else + #define FLECS_OS_API_STDCPP_API +#endif + +#endif + diff --git a/fggl/ecs2/flecs/examples/os_api/stdcpp/include/flecs_os_api_stdcpp.h b/fggl/ecs2/flecs/examples/os_api/stdcpp/include/flecs_os_api_stdcpp.h new file mode 100644 index 0000000000000000000000000000000000000000..2bd53b9541cdf9c80b17c329d35eb7eb124ef074 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/stdcpp/include/flecs_os_api_stdcpp.h @@ -0,0 +1,18 @@ +#ifndef FLECS_OS_API_STDCPP_H +#define FLECS_OS_API_STDCPP_H + +/* This generated file contains includes for project dependencies */ +#include "flecs-os_api-stdcpp/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_OS_API_STDCPP_API +extern void stdcpp_set_os_api(void); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/fggl/ecs2/flecs/examples/os_api/stdcpp/project.json b/fggl/ecs2/flecs/examples/os_api/stdcpp/project.json new file mode 100644 index 0000000000000000000000000000000000000000..3fee71ac531b3781c6648f182bd2f84932ebecb8 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/stdcpp/project.json @@ -0,0 +1,17 @@ +{ + "id": "flecs.os_api.stdcpp", + "type": "package", + "value": { + "author": "mclevi", + "description": "C++ OS API implementation", + "language": "c++", + "use": [ + "flecs" + ] + }, + "lang.cpp": { + "${os linux}": { + "lib": ["pthread"] + } + } +} diff --git a/fggl/ecs2/flecs/examples/os_api/stdcpp/src/flecs-os_api-stdcpp.cpp b/fggl/ecs2/flecs/examples/os_api/stdcpp/src/flecs-os_api-stdcpp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2fc4811beed1188bfc5d635b54e94215500ddf9 --- /dev/null +++ b/fggl/ecs2/flecs/examples/os_api/stdcpp/src/flecs-os_api-stdcpp.cpp @@ -0,0 +1,125 @@ +#include "flecs_os_api_stdcpp.h" +#include <thread> +#include <mutex> +#include <condition_variable> + +static +ecs_os_thread_t stdcpp_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + std::thread *thread = new std::thread{callback,arg}; + return reinterpret_cast<ecs_os_thread_t>(thread); +} + +static +void* stdcpp_thread_join( + ecs_os_thread_t thread) +{ + void *arg = nullptr; + std::thread *thr = reinterpret_cast<std::thread*>(thread); + thr->join(); + delete thr; + return arg; +} + +static +int32_t stdcpp_ainc(int32_t *count) { + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + return InterlockedIncrement(reinterpret_cast<LONG*>(count)); +#endif + return value; +} + +static +int32_t stdcpp_adec(int32_t *count) { + int value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + return InterlockedDecrement(reinterpret_cast<LONG*>(count)); +#endif + return value; +} + +static +ecs_os_mutex_t stdcpp_mutex_new(void) { + std::mutex *mutex = new std::mutex; + return reinterpret_cast<ecs_os_mutex_t>(mutex); +} + +static +void stdcpp_mutex_free(ecs_os_mutex_t m) { + std::mutex*mutex = reinterpret_cast<std::mutex*>(m); + delete mutex; +} + +static +void stdcpp_mutex_lock(ecs_os_mutex_t m) { + std::mutex*mutex = reinterpret_cast<std::mutex*>(m); + mutex->lock(); +} + +static +void stdcpp_mutex_unlock(ecs_os_mutex_t m) { + std::mutex *mutex = reinterpret_cast<std::mutex*>(m); + mutex->unlock(); +} + +static +ecs_os_cond_t stdcpp_cond_new(void) { + std::condition_variable_any* cond = new std::condition_variable_any{}; + return reinterpret_cast<ecs_os_cond_t>(cond); +} + +static +void stdcpp_cond_free(ecs_os_cond_t c) { + std::condition_variable_any *cond = reinterpret_cast<std::condition_variable_any*>(c); + delete cond; +} + +static +void stdcpp_cond_signal(ecs_os_cond_t c) { + std::condition_variable_any *cond = reinterpret_cast<std::condition_variable_any*>(c); + cond->notify_one(); +} + +static +void stdcpp_cond_broadcast(ecs_os_cond_t c) { + std::condition_variable_any*cond = reinterpret_cast<std::condition_variable_any*>(c); + cond->notify_all(); +} + +static +void stdcpp_cond_wait(ecs_os_cond_t c, ecs_os_mutex_t m) { + std::condition_variable_any* cond = reinterpret_cast<std::condition_variable_any*>(c); + std::mutex* mutex = reinterpret_cast<std::mutex*>(m); + cond->wait(*mutex); +} + +void stdcpp_set_os_api(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = stdcpp_thread_new; + api.thread_join_ = stdcpp_thread_join; + api.ainc_ = stdcpp_ainc; + api.adec_ = stdcpp_adec; + api.mutex_new_ = stdcpp_mutex_new; + api.mutex_free_ = stdcpp_mutex_free; + api.mutex_lock_ = stdcpp_mutex_lock; + api.mutex_unlock_ = stdcpp_mutex_unlock; + api.cond_new_ = stdcpp_cond_new; + api.cond_free_ = stdcpp_cond_free; + api.cond_signal_ = stdcpp_cond_signal; + api.cond_broadcast_ = stdcpp_cond_broadcast; + api.cond_wait_ = stdcpp_cond_wait; + + ecs_os_set_api(&api); +} diff --git a/fggl/ecs2/flecs/flecs.c b/fggl/ecs2/flecs/flecs.c new file mode 100644 index 0000000000000000000000000000000000000000..faf6df994f328084a5807126334ca8f4db626ce3 --- /dev/null +++ b/fggl/ecs2/flecs/flecs.c @@ -0,0 +1,28486 @@ +#ifndef FLECS_IMPL +#include "flecs.h" +#endif +#ifndef FLECS_PRIVATE_H +#define FLECS_PRIVATE_H + +#ifndef FLECS_TYPES_PRIVATE_H +#define FLECS_TYPES_PRIVATE_H + +#ifndef __MACH__ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <time.h> +#include <ctype.h> +#include <math.h> + +#ifdef _MSC_VER +//FIXME +#else +#include <sys/param.h> /* attempt to define endianness */ +#endif +#ifdef linux +# include <endian.h> /* attempt to define endianness */ +#endif + +/** + * @file entity_index.h + * @brief Entity index data structure. + * + * The entity index stores the table, row for an entity id. It is implemented as + * a sparse set. This file contains convenience macro's for working with the + * entity index. + */ + +#ifndef FLECS_ENTITY_INDEX_H +#define FLECS_ENTITY_INDEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ecs_eis_get(world, entity) flecs_sparse_get((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_get_any(world, entity) flecs_sparse_get_any((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_set(world, entity, ...) (flecs_sparse_set((world->store).entity_index, ecs_record_t, entity, (__VA_ARGS__))) +#define ecs_eis_ensure(world, entity) flecs_sparse_ensure((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_delete(world, entity) flecs_sparse_remove((world->store).entity_index, entity) +#define ecs_eis_set_generation(world, entity) flecs_sparse_set_generation((world->store).entity_index, entity) +#define ecs_eis_is_alive(world, entity) flecs_sparse_is_alive((world->store).entity_index, entity) +#define ecs_eis_get_current(world, entity) flecs_sparse_get_alive((world->store).entity_index, entity) +#define ecs_eis_exists(world, entity) flecs_sparse_exists((world->store).entity_index, entity) +#define ecs_eis_recycle(world) flecs_sparse_new_id((world->store).entity_index) +#define ecs_eis_clear_entity(world, entity, is_watched) ecs_eis_set((world->store).entity_index, entity, &(ecs_record_t){NULL, is_watched}) +#define ecs_eis_set_size(world, size) flecs_sparse_set_size((world->store).entity_index, size) +#define ecs_eis_count(world) flecs_sparse_count((world->store).entity_index) +#define ecs_eis_clear(world) flecs_sparse_clear((world->store).entity_index) +#define ecs_eis_copy(world) flecs_sparse_copy((world->store).entity_index) +#define ecs_eis_free(world) flecs_sparse_free((world->store).entity_index) +#define ecs_eis_memory(world, allocd, used) flecs_sparse_memory((world->store).entity_index, allocd, used) + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file bitset.h + * @brief Bitset datastructure. + * + * Simple bitset implementation. The bitset allows for storage of arbitrary + * numbers of bits. + */ + +#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); + +/** Deinialize bitset. */ +FLECS_DBG_API +void flecs_bitset_deinit( + 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 sparse.h + * @brief Sparse set datastructure. + * + * This is an implementation of a paged sparse set that stores the payload in + * the sparse array. + * + * A sparse set has a dense and a sparse array. The sparse array is directly + * indexed by a 64 bit identifier. The sparse element is linked with a dense + * element, which allows for liveliness checking. The liveliness check itself + * can be performed by doing (psuedo code): + * dense[sparse[sparse_id].dense] == sparse_id + * + * To ensure that the sparse array doesn't have to grow to a large size when + * using large sparse_id's, the sparse set uses paging. This cuts up the array + * into several pages of 4096 elements. When an element is set, the sparse set + * ensures that the corresponding page is created. The page associated with an + * id is determined by shifting a bit 12 bits to the right. + * + * The sparse set keeps track of a generation count per id, which is increased + * each time an id is deleted. The generation is encoded in the returned id. + * + * This sparse set implementation stores payload in the sparse array, which is + * not typical. The reason for this is to guarantee that (in combination with + * paging) the returned payload pointers are stable. This allows for various + * optimizations in the parts of the framework that uses the sparse set. + * + * The sparse set has been designed so that new ids can be generated in bulk, in + * an O(1) operation. The way this works is that once a dense-sparse pair is + * created, it is never unpaired. Instead it is moved to the end of the dense + * array, and the sparse set stores an additional count to keep track of the + * last alive id in the sparse set. To generate new ids in bulk, the sparse set + * only needs to increase this count by the number of requested ids. + */ + +#ifndef FLECS_SPARSE_H +#define FLECS_SPARSE_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** Create new sparse set */ +FLECS_DBG_API +ecs_sparse_t* _flecs_sparse_new( + ecs_size_t elem_size); + +#define flecs_sparse_new(type)\ + _flecs_sparse_new(sizeof(type)) + +/** Set id source. This allows the sparse set to use an external variable for + * issuing and increasing new ids. */ +FLECS_DBG_API +void flecs_sparse_set_id_source( + ecs_sparse_t *sparse, + uint64_t *id_source); + +/** Free sparse set */ +FLECS_DBG_API +void flecs_sparse_free( + 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(sparse, type)\ + ((type*)_flecs_sparse_add(sparse, sizeof(type))) + +/** 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); + +/** Generate or recycle new ids in bulk. The returned pointer points directly to + * the internal dense array vector with sparse ids. Operations on the sparse set + * can (and likely will) modify the contents of the buffer. */ +FLECS_DBG_API +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t count); + +/** Remove an element */ +FLECS_DBG_API +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t id); + +/** Remove an element, return pointer to the value in the sparse array */ +FLECS_DBG_API +void* _flecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_remove_get(sparse, type, index)\ + ((type*)_flecs_sparse_remove_get(sparse, sizeof(type), index)) + +/** Override the generation count for a specific id */ +FLECS_DBG_API +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t id); + +/** Check whether an id has ever been issued. */ +FLECS_DBG_API +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Test if id is alive, which requires the generation count tp match. */ +FLECS_DBG_API +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Return identifier with current generation set. */ +FLECS_DBG_API +uint64_t flecs_sparse_get_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(sparse, type, index)\ + ((type*)_flecs_sparse_get_dense(sparse, sizeof(type), index)) + +/** Get the number of alive elements in the sparse set. */ +FLECS_DBG_API +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse); + +/** Return total number of allocated elements in the dense array */ +FLECS_DBG_API +int32_t flecs_sparse_size( + 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(sparse, type, index)\ + ((type*)_flecs_sparse_get(sparse, sizeof(type), index)) + +/** Like get_sparse, but don't care whether element is alive or not. */ +FLECS_DBG_API +void* _flecs_sparse_get_any( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_get_any(sparse, type, index)\ + ((type*)_flecs_sparse_get_any(sparse, sizeof(type), 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(sparse, type, index)\ + ((type*)_flecs_sparse_ensure(sparse, sizeof(type), index)) + +/** Set value. */ +FLECS_DBG_API +void* _flecs_sparse_set( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id, + void *value); + +#define flecs_sparse_set(sparse, type, index, value)\ + ((type*)_flecs_sparse_set(sparse, sizeof(type), index, value)) + +/** Get pointer to ids (alive and not alive). Use with count() or size(). */ +FLECS_DBG_API +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse); + +/** Set size of the dense array. */ +FLECS_DBG_API +void flecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count); + +/** Copy sparse set into a new sparse set. */ +FLECS_DBG_API +ecs_sparse_t* flecs_sparse_copy( + const ecs_sparse_t *src); + +/** Restore sparse set into destination sparse set. */ +FLECS_DBG_API +void flecs_sparse_restore( + ecs_sparse_t *dst, + const ecs_sparse_t *src); + +/** Get memory usage of sparse set. */ +FLECS_DBG_API +void flecs_sparse_memory( + ecs_sparse_t *sparse, + int32_t *allocd, + int32_t *used); + + +/* Publicly exposed APIs + * The flecs_ functions aren't exposed directly as this can cause some + * optimizers to not consider them for link time optimization. */ + +FLECS_API +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size); + +#define ecs_sparse_new(type)\ + _ecs_sparse_new(sizeof(type)) + +FLECS_API +void* _ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_add(sparse, type)\ + ((type*)_ecs_sparse_add(sparse, sizeof(type))) + +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(sparse, type, index)\ + ((type*)_ecs_sparse_get_dense(sparse, sizeof(type), index)) + +FLECS_API +void* _ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define ecs_sparse_get(sparse, type, index)\ + ((type*)_ecs_sparse_get(sparse, sizeof(type), index)) + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file switch_list.h + * @brief Interleaved linked list for storing mutually exclusive values. + * + * Datastructure that stores N interleaved linked lists in an array. + * This allows for efficient storage of elements with mutually exclusive values. + * Each linked list has a header element which points to the index in the array + * that stores the first node of the list. Each list node points to the next + * array element. + * + * The datastructure needs to be created with min and max values, so that it can + * allocate an array of headers that can be directly indexed by the value. The + * values are stored in a contiguous array, which allows for the values to be + * iterated without having to follow the linked list nodes. + * + * The datastructure allows for efficient storage and retrieval for values with + * mutually exclusive values, such as enumeration values. The linked list allows + * an application to obtain all elements for a given (enumeration) value without + * having to search. + * + * While the list accepts 64 bit values, it only uses the lower 32bits of the + * value for selecting the correct linked list. + */ + +#ifndef FLECS_SWITCH_LIST_H +#define FLECS_SWITCH_LIST_H + + +typedef struct flecs_switch_header_t { + int32_t element; /* First element for value */ + int32_t count; /* Number of elements for value */ +} flecs_switch_header_t; + +typedef struct flecs_switch_node_t { + int32_t next; /* Next node in list */ + int32_t prev; /* Prev node in list */ +} flecs_switch_node_t; + +struct ecs_switch_t { + uint64_t min; /* Minimum value the switch can store */ + uint64_t max; /* Maximum value the switch can store */ + flecs_switch_header_t *headers; /* Array with headers, indexed by value */ + ecs_vector_t *nodes; /* Vector with nodes, of type flecs_switch_node_t */ + ecs_vector_t *values; /* Vector with values, of type uint64_t */ +}; + +/** Create new switch. */ +FLECS_DBG_API +ecs_switch_t* flecs_switch_new( + uint64_t min, + uint64_t max, + int32_t elements); + +/** Free switch. */ +FLECS_DBG_API +void flecs_switch_free( + ecs_switch_t *sw); + +/** Add element to switch, initialize value to 0 */ +FLECS_DBG_API +void flecs_switch_add( + ecs_switch_t *sw); + +/** Set number of elements in switch list */ +FLECS_DBG_API +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count); + +/** Ensure that element exists. */ +FLECS_DBG_API +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count); + +/** Add n elements. */ +FLECS_DBG_API +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count); + +/** Set value of element. */ +FLECS_DBG_API +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value); + +/** Remove element. */ +FLECS_DBG_API +void flecs_switch_remove( + ecs_switch_t *sw, + int32_t element); + +/** Get value for element. */ +FLECS_DBG_API +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element); + +/** Swap element. */ +FLECS_DBG_API +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2); + +/** Get vector with all values. Use together with count(). */ +FLECS_DBG_API +ecs_vector_t* flecs_switch_values( + const ecs_switch_t *sw); + +/** Return number of different values. */ +FLECS_DBG_API +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value); + +/** Return first element for value. */ +FLECS_DBG_API +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value); + +/** Return next element for value. Use with first(). */ +FLECS_DBG_API +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t elem); + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file hashmap.h + * @brief Hashmap datastructure. + * + * Datastructure that computes a hash to store & retrieve values. Similar to + * ecs_map_t, but allows for arbitrary keytypes. + */ + +#ifndef FLECS_HASHMAP_H +#define FLECS_HASHMAP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + ecs_hash_value_action_t hash; + ecs_compare_action_t compare; + ecs_size_t key_size; + ecs_size_t value_size; + ecs_map_t *impl; +} ecs_hashmap_t; + +typedef struct { + ecs_map_iter_t it; + struct 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 +ecs_hashmap_t _flecs_hashmap_new( + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare); + +#define flecs_hashmap_new(K, V, compare, hash)\ + _flecs_hashmap_new(ECS_SIZEOF(K), ECS_SIZEOF(V), compare, hash) + +FLECS_DBG_API +void flecs_hashmap_free( + 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( + const ecs_hashmap_t map, + ecs_size_t key_size, + 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( + const 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( + const 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( + const 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 +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 + +#define ECS_MAX_JOBS_PER_WORKER (16) + +/** These values are used to verify validity of the pointers passed into the API + * and to allow for passing a thread as a world to some API calls (this allows + * for transparently passing thread context to API functions) */ +#define ECS_WORLD_MAGIC (0x65637377) +#define ECS_STAGE_MAGIC (0x65637374) + +/* Maximum number of entities that can be added in a single operation. + * Increasing this value will increase consumption of stack space. */ +#define ECS_MAX_ADD_REMOVE (32) + +/** Type used for internal string hashmap */ +typedef struct ecs_string_t { + char *value; + ecs_size_t length; + uint64_t hash; +} ecs_string_t; + +/** Component-specific data */ +typedef struct ecs_type_info_t { + EcsComponentLifecycle lifecycle; /* Component lifecycle callbacks */ + ecs_entity_t component; + ecs_size_t size; + ecs_size_t alignment; + bool lifecycle_set; +} ecs_type_info_t; + +/* Table event type for notifying tables of world events */ +typedef enum ecs_table_eventkind_t { + EcsTableQueryMatch, + EcsTableQueryUnmatch, + EcsTableTriggerMatch, + EcsTableComponentInfo +} ecs_table_eventkind_t; + +typedef struct ecs_table_event_t { + ecs_table_eventkind_t kind; + + /* Query event */ + ecs_query_t *query; + int32_t matched_table_index; + + /* Component info event */ + ecs_entity_t component; + + /* Trigger match */ + ecs_entity_t event; + + /* If the nubmer of fields gets out of hand, this can be turned into a union + * but since events are very temporary objects, this works for now and makes + * initializing an event a bit simpler. */ +} ecs_table_event_t; + +/** A component column. */ +struct ecs_column_t { + ecs_vector_t *data; /**< Column data */ + int16_t size; /**< Column element size */ + int16_t alignment; /**< Column element alignment */ +}; + +/** A switch column. */ +typedef struct ecs_sw_column_t { + ecs_switch_t *data; /**< Column data */ + ecs_type_t type; /**< Switch type */ +} ecs_sw_column_t; + +/** A bitset column. */ +typedef struct ecs_bs_column_t { + ecs_bitset_t data; /**< Column data */ +} ecs_bs_column_t; + +/** Stage-specific component data */ +struct ecs_data_t { + ecs_vector_t *entities; /**< Entity identifiers */ + ecs_vector_t *record_ptrs; /**< Ptrs to records in main entity index */ + ecs_column_t *columns; /**< Component columns */ + ecs_sw_column_t *sw_columns; /**< Switch columns */ + ecs_bs_column_t *bs_columns; /**< Bitset columns */ +}; + +/** Small footprint data structure for storing data associated with a table. */ +typedef struct ecs_table_leaf_t { + ecs_table_t *table; + ecs_type_t type; + ecs_data_t *data; +} ecs_table_leaf_t; + +/** Flags for quickly checking for special properties of a table. */ +#define EcsTableHasBuiltins 1u /**< Does table have builtin components */ +#define EcsTableIsPrefab 2u /**< Does the table store prefabs */ +#define EcsTableHasIsA 4u /**< Does the table type has IsA */ +#define EcsTableHasModule 8u /**< Does the table have module data */ +#define EcsTableHasXor 32u /**< Does the table type has XOR */ +#define EcsTableIsDisabled 64u /**< Does the table type has EcsDisabled */ +#define EcsTableHasCtors 128u +#define EcsTableHasDtors 256u +#define EcsTableHasCopy 512u +#define EcsTableHasMove 1024u +#define EcsTableHasOnAdd 2048u +#define EcsTableHasOnRemove 4096u +#define EcsTableHasOnSet 8192u +#define EcsTableHasUnSet 16384u +#define EcsTableHasMonitors 32768u +#define EcsTableHasSwitch 65536u +#define EcsTableHasDisabled 131072u + +/* Composite constants */ +#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) +#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasSwitch | EcsTableHasDisabled) +#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasSwitch | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet | EcsTableHasMonitors) +#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet | EcsTableHasMonitors) + +/** Edge used for traversing the table graph. */ +typedef struct ecs_edge_t { + ecs_table_t *add; /**< Edges traversed when adding */ + ecs_table_t *remove; /**< Edges traversed when removing */ +} ecs_edge_t; + +/** Quey matched with table with backref to query table administration. + * This type is used to store a matched query together with the array index of + * where the table is stored in the query administration. This type is used when + * an action that originates on a table needs to invoke a query (system) and a + * fast lookup is required for the query administration, as is the case with + * OnSet and Monitor systems. */ +typedef struct ecs_matched_query_t { + ecs_query_t *query; /**< The query matched with the table */ + int32_t matched_table_index; /**< Table index in the query type */ +} ecs_matched_query_t; + +/** A table is the Flecs equivalent of an archetype. Tables store all entities + * with a specific set of components. Tables are automatically created when an + * entity has a set of components not previously observed before. When a new + * table is created, it is automatically matched with existing queries */ +struct ecs_table_t { + uint64_t id; /**< Table id in sparse set */ + ecs_type_t type; /**< Identifies table type in type_index */ + ecs_flags32_t flags; /**< Flags for testing table properties */ + int32_t column_count; /**< Number of data columns in table */ + + ecs_data_t *data; /**< Component storage */ + ecs_type_info_t **c_info; /**< Cached pointers to component info */ + + ecs_edge_t *lo_edges; /**< Edges to other tables */ + ecs_map_t *hi_edges; + + ecs_vector_t *queries; /**< Queries matched with table */ + ecs_vector_t *monitors; /**< Monitor systems matched with table */ + ecs_vector_t **on_set; /**< OnSet systems, broken up by column */ + ecs_vector_t *on_set_all; /**< All OnSet systems */ + ecs_vector_t *on_set_override; /**< All OnSet systems with overrides */ + ecs_vector_t *un_set_all; /**< All UnSet systems */ + + int32_t *dirty_state; /**< Keep track of changes in columns */ + int32_t alloc_count; /**< Increases when columns are reallocd */ + + int32_t sw_column_count; + int32_t sw_column_offset; + int32_t bs_column_count; + int32_t bs_column_offset; + + int32_t lock; +}; + +/* Sparse query column */ +typedef struct flecs_sparse_column_t { + ecs_sw_column_t *sw_column; + ecs_entity_t sw_case; + int32_t signature_column_index; +} flecs_sparse_column_t; + +/* Bitset query column */ +typedef struct flecs_bitset_column_t { + ecs_bs_column_t *bs_column; + int32_t column_index; +} flecs_bitset_column_t; + +/** Type containing data for a table matched with a query. */ +typedef struct ecs_matched_table_t { + int32_t *columns; /**< Mapping from query terms to table columns */ + ecs_table_t *table; /**< The current table. */ + ecs_data_t *data; /**< Table component data */ + ecs_id_t *ids; /**< Resolved (component) ids for current table */ + ecs_entity_t *subjects; /**< Subjects (sources) of ids */ + ecs_type_t *types; /**< Types for ids for current table */ + ecs_size_t *sizes; /**< Sizes for ids for current table */ + ecs_ref_t *references; /**< Cached components for non-this terms */ + + ecs_vector_t *sparse_columns; /**< Column ids of sparse columns */ + ecs_vector_t *bitset_columns; /**< Column ids with disabled flags */ + int32_t *monitor; /**< Used to monitor table for changes */ + int32_t rank; /**< Rank used to sort tables */ +} ecs_matched_table_t; + +/** Type used to track location of table in queries' table lists. + * When a table becomes empty or non-empty a signal is sent to a query, which + * moves the table to or from an empty list. While this ensures that when + * iterating no time is spent on iterating over empty tables, doing a linear + * search for the table in either list can take a significant amount of time if + * a query is matched with many tables. + * + * To avoid a linear search, the query has a map with table indices that can + * return the location of the table in either list in constant time. + * + * If a table is matched multiple times by a query, such as can happen when a + * query matches pairs, a table can occupy multiple indices. + */ +typedef struct ecs_table_indices_t { + int32_t *indices; /* If indices are negative, table is in empty list */ + int32_t count; +} ecs_table_indices_t; + +/** Type storing an entity range within a table. + * This type is used for iterating in orer across archetypes. A sorting function + * constructs a list of the ranges across archetypes that are in order so that + * when the query iterates over the archetypes, it only needs to iterate the + * list of ranges. */ +typedef struct ecs_table_slice_t { + ecs_matched_table_t *table; /**< Reference to the matched table */ + int32_t start_row; /**< Start of range */ + int32_t count; /**< Number of entities in range */ +} ecs_table_slice_t; + +#define EcsQueryNeedsTables (1) /* Query needs matching with tables */ +#define EcsQueryMonitor (2) /* Query needs to be registered as a monitor */ +#define EcsQueryOnSet (4) /* Query needs to be registered as on_set system */ +#define EcsQueryUnSet (8) /* Query needs to be registered as un_set system */ +#define EcsQueryMatchDisabled (16) /* Does query match disabled */ +#define EcsQueryMatchPrefab (32) /* Does query match prefabs */ +#define EcsQueryHasRefs (64) /* Does query have references */ +#define EcsQueryHasTraits (128) /* Does query have pairs */ +#define EcsQueryIsSubquery (256) /* Is query a subquery */ +#define EcsQueryIsOrphaned (512) /* Is subquery orphaned */ +#define EcsQueryHasOutColumns (1024) /* Does query have out columns */ +#define EcsQueryHasOptional (2048) /* Does query have optional columns */ + +#define EcsQueryNoActivation (EcsQueryMonitor | EcsQueryOnSet | EcsQueryUnSet) + +/* Query event type for notifying queries of world events */ +typedef enum ecs_query_eventkind_t { + EcsQueryTableMatch, + EcsQueryTableEmpty, + EcsQueryTableNonEmpty, + EcsQueryTableRematch, + EcsQueryTableUnmatch, + EcsQueryOrphan +} ecs_query_eventkind_t; + +typedef struct ecs_query_event_t { + ecs_query_eventkind_t kind; + ecs_table_t *table; + ecs_query_t *parent_query; +} ecs_query_event_t; + +/** Query that is automatically matched against active tables */ +struct ecs_query_t { + /* Signature of query */ + ecs_filter_t filter; + + /* Reference to world */ + ecs_world_t *world; + + /* Tables matched with query */ + ecs_vector_t *tables; + ecs_vector_t *empty_tables; + ecs_map_t *table_indices; + + /* Handle to system (optional) */ + ecs_entity_t system; + + /* Used for sorting */ + ecs_entity_t order_by_component; + ecs_order_by_action_t order_by; + ecs_vector_t *table_slices; + + /* Used for table sorting */ + ecs_entity_t group_by_id; + ecs_group_by_action_t group_by; + void *group_by_ctx; + ecs_ctx_free_t group_by_ctx_free; + + /* Subqueries */ + ecs_query_t *parent; + ecs_vector_t *subqueries; + + /* The query kind determines how it is registered with tables */ + ecs_flags32_t flags; + + uint64_t id; /* Id of query in query storage */ + int32_t cascade_by; /* Identify CASCADE column */ + int32_t match_count; /* How often have tables been (un)matched */ + int32_t prev_match_count; /* Used to track if sorting is needed */ + + bool needs_reorder; /* Whether next iteration should reorder */ + bool constraints_satisfied; /* Are all term constraints satisfied */ +}; + +/** Event mask */ +#define EcsEventAdd (1) +#define EcsEventRemove (2) + +/** Triggers for a specific id */ +typedef struct ecs_id_trigger_t { + ecs_map_t *on_add_triggers; + ecs_map_t *on_remove_triggers; + ecs_map_t *on_set_triggers; + ecs_map_t *un_set_triggers; +} ecs_id_trigger_t; + +/** Keep track of how many [in] columns are active for [out] columns of OnDemand + * systems. */ +typedef struct ecs_on_demand_out_t { + ecs_entity_t system; /* Handle to system */ + int32_t count; /* Total number of times [out] columns are used */ +} ecs_on_demand_out_t; + +/** Keep track of which OnDemand systems are matched with which [in] columns */ +typedef struct ecs_on_demand_in_t { + int32_t count; /* Number of active systems with [in] column */ + ecs_vector_t *systems; /* Systems that have this column as [out] column */ +} ecs_on_demand_in_t; + +/** Types for deferred operations */ +typedef enum ecs_op_kind_t { + EcsOpNew, + EcsOpClone, + EcsOpBulkNew, + EcsOpAdd, + EcsOpRemove, + EcsOpSet, + EcsOpMut, + EcsOpModified, + EcsOpDelete, + EcsOpClear, + EcsOpEnable, + EcsOpDisable +} ecs_op_kind_t; + +typedef struct ecs_op_1_t { + ecs_entity_t entity; /* Entity id */ + void *value; /* Value (used for set / get_mut) */ + ecs_size_t size; /* Size of value */ + bool clone_value; /* Clone entity with value (used for clone) */ +} ecs_op_1_t; + +typedef struct ecs_op_n_t { + ecs_entity_t *entities; + void **bulk_data; + int32_t count; +} ecs_op_n_t; + +typedef struct ecs_op_t { + ecs_op_kind_t kind; /* Operation kind */ + ecs_entity_t component; /* Single component (components.count = 1) */ + ecs_ids_t components; /* Multiple components */ + union { + ecs_op_1_t _1; + ecs_op_n_t _n; + } is; +} ecs_op_t; + +/** A stage is a data structure in which delta's are stored until it is safe to + * merge those delta's with the main world stage. A stage allows flecs systems + * to arbitrarily add/remove/set components and create/delete entities while + * iterating. Additionally, worker threads have their own stage that lets them + * mutate the state of entities without requiring locks. */ +struct ecs_stage_t { + int32_t magic; /* Magic number to verify thread pointer */ + int32_t id; /* Unique id that identifies the stage */ + + /* Are operations deferred? */ + int32_t defer; + ecs_vector_t *defer_queue; + + 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_vector_t *post_frame_actions; + + /* Namespacing */ + ecs_table_t *scope_table; /* Table for current scope */ + ecs_entity_t scope; /* Entity of current scope */ + ecs_entity_t with; /* Id to add by default to new entities */ + + /* Properties */ + bool auto_merge; /* Should this stage automatically merge? */ + bool asynchronous; /* Is stage asynchronous? (write only) */ +}; + +/* Component monitor */ +typedef struct ecs_monitor_t { + ecs_vector_t *queries; /* vector<ecs_query_t*> */ + bool is_dirty; /* Should queries be rematched? */ +} ecs_monitor_t; + +/* Component monitors */ +typedef struct ecs_monitor_set_t { + ecs_map_t *monitors; /* map<id, ecs_monitor_t> */ + bool is_dirty; /* Should monitors be evaluated? */ +} ecs_monitor_set_t; + +/* Relation monitors. TODO: implement generic monitor mechanism */ +typedef struct ecs_relation_monitor_t { + ecs_map_t *monitor_sets; /* map<relation_id, ecs_monitor_set_t> */ + bool is_dirty; /* Should monitor sets be evaluated? */ +} ecs_relation_monitor_t; + +/* Payload for table index which returns all tables for a given component, with + * the column of the component in the table. */ +typedef struct ecs_table_record_t { + ecs_table_t *table; + int32_t column; + int32_t count; +} ecs_table_record_t; + +/* Payload for id index which contains all datastructures for an id. */ +struct ecs_id_record_t { + /* All tables that contain the id */ + ecs_map_t *table_index; /* map<table_id, ecs_table_record_t> */ + + ecs_entity_t on_delete; /* Cleanup action for removing id */ + ecs_entity_t on_delete_object; /* Cleanup action for removing object */ +}; + +typedef struct ecs_store_t { + /* Entity lookup */ + ecs_sparse_t *entity_index; /* sparse<entity, ecs_record_t> */ + + /* Table lookup by id */ + ecs_sparse_t *tables; /* sparse<table_id, ecs_table_t> */ + + /* Table lookup by hash */ + ecs_hashmap_t table_map; /* hashmap<ecs_ids_t, ecs_table_t*> */ + + /* Root table */ + ecs_table_t root; +} ecs_store_t; + +/** Supporting type to store looked up or derived entity data */ +typedef struct ecs_entity_info_t { + ecs_record_t *record; /* Main stage record in entity index */ + ecs_table_t *table; /* Table. Not set if entity is empty */ + ecs_data_t *data; /* Stage-specific table columns */ + int32_t row; /* Actual row (stripped from is_watched bit) */ + bool is_watched; /* Is entity being watched */ +} ecs_entity_info_t; + +/** Supporting type to store looked up component data in specific table */ +typedef struct ecs_column_info_t { + ecs_entity_t id; + const ecs_type_info_t *ci; + int32_t column; +} ecs_column_info_t; + +/* fini actions */ +typedef struct ecs_action_elem_t { + ecs_fini_action_t action; + void *ctx; +} ecs_action_elem_t; + +/* Alias */ +typedef struct ecs_alias_t { + char *name; + ecs_entity_t entity; +} ecs_alias_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 { + int32_t magic; /* Magic number to verify world pointer */ + + /* -- Type metadata -- */ + + ecs_map_t *id_index; /* map<id, ecs_id_record_t> */ + ecs_map_t *id_triggers; /* map<id, ecs_id_trigger_t> */ + ecs_sparse_t *type_info; /* sparse<type_id, type_info_t> */ + + /* Is entity range checking enabled? */ + bool range_check_enabled; + + + /* -- Data storage -- */ + + ecs_store_t store; + + + /* -- Storages for API objects -- */ + + ecs_sparse_t *queries; /* sparse<query_id, ecs_query_t> */ + ecs_sparse_t *triggers; /* sparse<query_id, ecs_trigger_t> */ + ecs_sparse_t *observers; /* sparse<query_id, ecs_observer_t> */ + + + /* Keep track of components that were added/removed to/from monitored + * entities. Monitored entities are entities that a query has matched with + * specifically, as is the case with PARENT / CASCADE columns, FromEntity + * columns and columns matched from prefabs. + * When these entities change type, queries may have to be rematched. + * Queries register themselves as component monitors for specific components + * and when these components change they are rematched. The component + * monitors are evaluated during a merge. */ + ecs_relation_monitor_t monitors; + + /* -- Systems -- */ + + ecs_entity_t pipeline; /* Current pipeline */ + ecs_map_t *on_activate_components; /* Trigger on activate of [in] column */ + ecs_map_t *on_enable_components; /* Trigger on enable of [in] column */ + ecs_vector_t *fini_tasks; /* Tasks to execute on ecs_fini */ + + + /* -- Lookup Indices -- */ + + ecs_map_t *type_handles; /* Handles to named types */ + + + /* -- Aliasses -- */ + + ecs_hashmap_t aliases; + ecs_hashmap_t symbols; + + + /* -- Staging -- */ + + ecs_stage_t stage; /* Main storage */ + ecs_vector_t *worker_stages; /* Stages for threads */ + + + /* -- Hierarchy administration -- */ + + const char *name_prefix; /* Remove prefix from C names in modules */ + + + /* -- 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 */ + + + /* -- Time management -- */ + + ecs_time_t world_start_time; /* Timestamp of simulation start */ + ecs_time_t frame_start_time; /* Timestamp of frame start */ + FLECS_FLOAT fps_sleep; /* Sleep time to prevent fps overshoot */ + + + /* -- Metrics -- */ + + ecs_world_info_t stats; + + + /* -- Settings from command line arguments -- */ + + int arg_fps; + int arg_threads; + + + /* -- World lock -- */ + + ecs_os_mutex_t mutex; /* Locks the world if locking enabled */ + ecs_os_mutex_t thr_sync; /* Used to signal threads at end of frame */ + ecs_os_cond_t thr_cond; /* Used to signal threads at end of frame */ + + + /* -- Defered operation count -- */ + + int32_t new_count; + int32_t bulk_new_count; + int32_t delete_count; + int32_t clear_count; + int32_t add_count; + int32_t remove_count; + int32_t set_count; + int32_t discard_count; + + + /* -- World state -- */ + + bool quit_workers; /* Signals worker threads to quit */ + bool is_readonly; /* Is world being progressed */ + bool is_fini; /* Is the world being cleaned up? */ + bool measure_frame_time; /* Time spent on each frame */ + bool measure_system_time; /* Time spent by each system */ + bool should_quit; /* Did a system signal that app should quit */ + bool locking_enabled; /* Lock world when in progress */ + + void *context; /* Application context */ + ecs_vector_t *fini_actions; /* Callbacks to execute when world exits */ +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Core bootstrap functions +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_TYPE_DECL(component)\ +static const ecs_entity_t __##component = ecs_id(component);\ +ECS_VECTOR_DECL(FLECS__T##component, ecs_entity_t, 1) + +#define ECS_TYPE_IMPL(component)\ +ECS_VECTOR_IMPL(FLECS__T##component, ecs_entity_t, &__##component, 1) + +/* Bootstrap world */ +void flecs_bootstrap( + ecs_world_t *world); + +ecs_type_t flecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity); + +#define flecs_bootstrap_component(world, id)\ + ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + }); + +#define flecs_bootstrap_tag(world, name)\ + ecs_set_name(world, name, (char*)&#name[ecs_os_strlen("Ecs")]);\ + ecs_set_symbol(world, name, #name);\ + ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world)) + + +/* 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_set_watch( + ecs_world_t *world, + ecs_entity_t entity); + +/* Obtain entity info */ +bool flecs_get_info( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_info_t *info); + +void flecs_run_monitors( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_vector_t *v_dst_monitors, + int32_t dst_row, + int32_t count, + ecs_vector_t *v_src_monitors); + +void flecs_register_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +void flecs_unregister_name( + ecs_world_t *world, + ecs_entity_t entity); + + +//////////////////////////////////////////////////////////////////////////////// +//// World API +//////////////////////////////////////////////////////////////////////////////// + +/* Get current stage */ +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr); + +/* Get current thread-specific stage from readonly world */ +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world); + +/* Get component callbacks */ +const ecs_type_info_t *flecs_get_c_info( + const ecs_world_t *world, + ecs_entity_t component); + +/* Get or create component callbacks */ +ecs_type_info_t * flecs_get_or_create_c_info( + 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 relation, + ecs_entity_t id); + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t relation, + 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_notify_queries( + ecs_world_t *world, + ecs_query_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); + +ecs_id_record_t* flecs_ensure_id_record( + const ecs_world_t *world, + ecs_id_t id); + +ecs_id_record_t* flecs_get_id_record( + const ecs_world_t *world, + ecs_id_t id); + +ecs_table_record_t* flecs_get_table_record( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +void flecs_clear_id_record( + const ecs_world_t *world, + ecs_id_t id); + +void flecs_triggers_notify( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count); + +ecs_map_t* flecs_triggers_get( + const ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event); + +void flecs_trigger_fini( + ecs_world_t *world, + ecs_trigger_t *trigger); + +void flecs_observer_fini( + ecs_world_t *world, + ecs_observer_t *observer); + +void flecs_use_intern( + ecs_entity_t entity, + const char *name, + ecs_vector_t **alias_vector); + + +//////////////////////////////////////////////////////////////////////////////// +//// Stage API +//////////////////////////////////////////////////////////////////////////////// + +/* Initialize stage data structures */ +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Deinitialize stage */ +void flecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Post-frame merge actions */ +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Delete table from stage */ +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table); + + +//////////////////////////////////////////////////////////////////////////////// +//// Defer API +//////////////////////////////////////////////////////////////////////////////// + +bool flecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component); + +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components); + +bool flecs_defer_clone( + ecs_world_t *world, + 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, + const ecs_ids_t *components, + void **component_data, + const ecs_entity_t **ids_out); + +bool flecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components); + +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components); + +bool flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added); + +bool flecs_defer_flush( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage); + +//////////////////////////////////////////////////////////////////////////////// +//// Type API +//////////////////////////////////////////////////////////////////////////////// + +/* Test if type_id_1 contains type_id_2 */ +ecs_entity_t flecs_type_contains( + const ecs_world_t *world, + ecs_type_t type_id_1, + ecs_type_t type_id_2, + bool match_all, + bool match_prefab); + +void flecs_run_add_actions( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_ids_t *added, + bool get_all, + bool run_on_set); + +void flecs_run_remove_actions( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_ids_t *removed); + +void flecs_run_set_systems( + ecs_world_t *world, + ecs_id_t component, + ecs_table_t *table, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count, + bool set_all); + + +//////////////////////////////////////////////////////////////////////////////// +//// Table API +//////////////////////////////////////////////////////////////////////////////// + +/** Find or create table for a set of components */ +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + const ecs_ids_t *type); + +/* Get table data */ +ecs_data_t *flecs_table_get_data( + const ecs_table_t *table); + +/* Get or create data */ +ecs_data_t *flecs_table_get_or_create_data( + ecs_table_t *table); + +/* Initialize columns for data */ +ecs_data_t* flecs_init_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *result); + +/* 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); + +/* Clear table data. Don't call OnRemove handlers. */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* 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_data_t *data, + ecs_entity_t entity, + ecs_record_t *record, + bool construct); + +/* Delete an entity from the table. */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t index, + bool destruct); + +/* Move a row from one table to another */ +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + bool construct); + +/* 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); + +/* Match table with filter */ +bool flecs_table_match_filter( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_filter_t *filter); + +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_type_t type, + ecs_id_t *ids, + int32_t *columns, + ecs_type_t *types, + ecs_entity_t *subjects, + ecs_size_t *sizes, + void **ptrs); + +/* Get dirty state for table columns */ +int32_t* flecs_table_get_dirty_state( + ecs_table_t *table); + +/* Get monitor for monitoring table changes */ +int32_t* flecs_table_get_monitor( + 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_free( + ecs_world_t *world, + ecs_table_t *table); + +/* Free table */ +void flecs_table_free_type( + ecs_table_t *table); + +/* Replace data */ +void flecs_table_replace_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* Merge data of one table into another table */ +ecs_data_t* flecs_table_merge( + ecs_world_t *world, + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data); + +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2); + +ecs_table_t *flecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *to_add, + ecs_ids_t *added); + +ecs_table_t *flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *to_remove, + ecs_ids_t *removed); + +void flecs_table_mark_dirty( + ecs_table_t *table, + ecs_entity_t component); + +const EcsComponent* flecs_component_from_id( + const ecs_world_t *world, + ecs_entity_t e); + +int32_t flecs_table_switch_from_case( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t add); + +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event); + +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table); + +ecs_column_t *ecs_table_column_for_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +//////////////////////////////////////////////////////////////////////////////// +//// Query API +//////////////////////////////////////////////////////////////////////////////// + +void flecs_query_set_iter( + ecs_world_t *world, + ecs_query_t *query, + ecs_iter_t *it, + int32_t table_index, + int32_t row, + int32_t count); + +void flecs_run_monitor( + ecs_world_t *world, + ecs_matched_query_t *monitor, + ecs_ids_t *components, + int32_t row, + int32_t count, + ecs_entity_t *entities); + +bool flecs_query_match( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_query_t *query, + ecs_match_failure_t *failure_info); + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event); + +void ecs_iter_init( + ecs_iter_t *it); + +void ecs_iter_fini( + ecs_iter_t *it); + +//////////////////////////////////////////////////////////////////////////////// +//// Time API +//////////////////////////////////////////////////////////////////////////////// + +void flecs_os_time_setup(void); + +uint64_t flecs_os_time_now(void); + +void flecs_os_time_sleep( + int32_t sec, + int32_t nanosec); + +/* Increase or reset timer resolution (Windows only) */ +FLECS_API +void flecs_increase_timer_resolution( + bool enable); + +//////////////////////////////////////////////////////////////////////////////// +//// Utilities +//////////////////////////////////////////////////////////////////////////////// + +uint64_t flecs_hash( + const void *data, + ecs_size_t length); + +/* Convert 64 bit signed integer to 16 bit */ +int8_t flflecs_to_i8( + int64_t v); + +/* Convert 64 bit signed integer to 16 bit */ +int16_t flecs_to_i16( + int64_t v); + +/* Convert 64 bit unsigned integer to 32 bit */ +uint32_t flecs_to_u32( + uint64_t v); + +/* Convert signed integer to size_t */ +size_t flecs_to_size_t( + int64_t size); + +/* Convert size_t to ecs_size_t */ +ecs_size_t flecs_from_size_t( + size_t size); + +/* 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); + +/* Get actual row from record row */ +int32_t flecs_record_to_row( + int32_t row, + bool *is_watched_out); + +/* Convert actual row to record row */ +int32_t flecs_row_to_record( + int32_t row, + bool is_watched); + +/* Convert type to entity array */ +ecs_ids_t flecs_type_to_ids( + ecs_type_t type); + +/* 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 */ +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +/* Compare function for entity ids which can be used with qsort */ +int flecs_entity_compare_qsort( + const void *e1, + const void *e2); + +uint64_t flecs_string_hash( + const void *ptr); + +ecs_hashmap_t flecs_table_hashmap_new(void); +ecs_hashmap_t flecs_string_hashmap_new(void); + +#define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) +void _assert_func( + bool cond, + const char *cond_str, + const char *file, + int32_t line, + const char *func); + +#endif + +static +char *ecs_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_vsprintf(result, fmt, args); + + return result; +} + +static +char* ecs_colorize( + char *msg, + bool enable_colors) +{ + ecs_strbuf_t buff = ECS_STRBUF_INIT; + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; + + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; + + if (!overrideColor) { + if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + isNum = false; + } + if (isStr && (isStr == ch) && prev != '\\') { + isStr = '\0'; + } else if (((ch == '\'') || (ch == '"')) && !isStr && + !isalpha(prev) && (prev != '\\')) + { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + isStr = ch; + } + + if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || + (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && + !isalpha(prev) && !isdigit(prev) && (prev != '_') && + (prev != '.')) + { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_GREEN); + isNum = true; + } + + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + isVar = false; + } + + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendstr(&buff, 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_appendstr(&buff, ECS_GREEN); + } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_RED); + } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_BLUE); + } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_MAGENTA); + } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_YELLOW); + } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_GREY); + } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_BOLD); + } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { + overrideColor = false; + if (enable_colors) ecs_strbuf_appendstr(&buff, 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_appendstr(&buff, ECS_NORMAL); + overrideColor = false; + isNum = false; + isStr = false; + isVar = false; + } + } + + if (!dontAppend) { + ecs_strbuf_appendstrn(&buff, ptr, 1); + } + + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } + } + + prev = ch; + } + + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } + + return ecs_strbuf_get(&buff); +} + +static int trace_indent = 0; +static int trace_level = 0; +static bool trace_color = true; + +static +void ecs_log_print( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) +{ + (void)level; + (void)line; + + if (level > trace_level) { + return; + } + + /* Massage filename so it doesn't take up too much space */ + char file_buf[256]; + ecs_os_strcpy(file_buf, file); + file = file_buf; + + char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); + } + + if (file_ptr) { + file = file_ptr + 1; + } else { + file = file_buf; + } + + char indent[32]; + int i; + for (i = 0; i < trace_indent; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } + indent[i * 2] = '\0'; + + char *msg_nocolor = ecs_vasprintf(fmt, args); + char *msg = ecs_colorize(msg_nocolor, trace_color); + + if (trace_color) { + if (level >= 0) { + ecs_os_log("%sinfo%s: %s%s%s%s:%s%d%s: %s", ECS_MAGENTA, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } else if (level == -2) { + ecs_os_warn("%swarn%s: %s%s%s%s:%s%d%s: %s", ECS_YELLOW, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } else if (level == -3) { + ecs_os_err("%serr%s: %s%s%s%s:%s%d%s: %s", ECS_RED, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } else if (level == -4) { + ecs_os_err("%sfatal%s: %s%s%s%s:%s%d%s: %s", ECS_RED, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } + } else { + if (level >= 0) { + ecs_os_log("info: %s%s:%d: %s", indent, file, line, msg); + } else if (level == -2) { + ecs_os_warn("warn: %s%s:%d: %s", indent, file, line, msg); + } else if (level == -3) { + ecs_os_err("err: %s%s:%d: %s", indent, file, line, msg); + } else if (level == -4) { + ecs_os_err("fatal: %s%s:%d: %s", indent, file, line, msg); + } + } + + ecs_os_free(msg); + ecs_os_free(msg_nocolor); +} + +void _ecs_trace( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(level, file, line, fmt, args); + va_end(args); +} + +void _ecs_warn( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(-2, file, line, fmt, args); + va_end(args); +} + +void _ecs_err( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(-3, file, line, fmt, args); + va_end(args); +} + +void _ecs_fatal( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(-4, file, line, fmt, args); + va_end(args); +} + +void ecs_log_push(void) { + trace_indent ++; +} + +void ecs_log_pop(void) { + trace_indent --; +} + +void ecs_tracing_enable( + int level) +{ + trace_level = level; +} + +void ecs_tracing_color_enable( + bool enabled) +{ + trace_color = enabled; +} + +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + if (trace_level >= -2) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + + if (column != -1) { + if (name) { + ecs_os_err("%s:%d: error: %s", name, column + 1, msg); + } else { + ecs_os_err("%d: error: %s", column + 1, msg); + } + } else { + if (name) { + ecs_os_err("%s: error: %s", name, msg); + } else { + ecs_os_err("error: %s", msg); + } + } + + ecs_os_err(" %s", expr); + + if (column != -1) { + ecs_os_err(" %*s^", column, ""); + } else { + ecs_os_err(""); + } + + ecs_os_free(msg); + } + + ecs_os_abort(); +} + +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 = ecs_vasprintf(fmt, args); + va_end(args); + _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + _ecs_fatal(file, line, "%s", ecs_strerror(err)); + } + + ecs_os_abort(); +} + +void _ecs_assert( + bool condition, + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (!condition) { + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + va_end(args); + _ecs_fatal(file, line, "assert(%s) %s (%s)", + cond_str, msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + _ecs_fatal(file, line, "assert(%s) %s", + cond_str, ecs_strerror(err)); + } + + ecs_os_abort(); + } +} + +void _ecs_deprecated( + const char *file, + int32_t line, + const char *msg) +{ + _ecs_err(file, line, "%s", msg); +} + +#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_TYPE_NOT_AN_ENTITY); + 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_OUT_OF_MEMORY); + ECS_ERR_STR(ECS_MODULE_UNDEFINED); + 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_HAS_NO_DATA); + ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); + ECS_ERR_STR(ECS_INVALID_WHILE_ITERATING); + ECS_ERR_STR(ECS_INVALID_FROM_WORKER); + ECS_ERR_STR(ECS_OUT_OF_RANGE); + ECS_ERR_STR(ECS_THREAD_ERROR); + ECS_ERR_STR(ECS_MISSING_OS_API); + ECS_ERR_STR(ECS_UNSUPPORTED); + ECS_ERR_STR(ECS_NO_OUT_COLUMNS); + ECS_ERR_STR(ECS_COLUMN_ACCESS_VIOLATION); + ECS_ERR_STR(ECS_DESERIALIZE_FORMAT_ERROR); + ECS_ERR_STR(ECS_TYPE_CONSTRAINT_VIOLATION); + ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); + ECS_ERR_STR(ECS_TYPE_INVALID_CASE); + ECS_ERR_STR(ECS_INCONSISTENT_NAME); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); + ECS_ERR_STR(ECS_INVALID_OPERATION); + ECS_ERR_STR(ECS_INVALID_DELETE); + ECS_ERR_STR(ECS_CYCLE_DETECTED); + ECS_ERR_STR(ECS_LOCKED_STORAGE); + } + + return "unknown error code"; +} + +ecs_data_t* flecs_init_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *result) +{ + ecs_type_t type = table->type; + int32_t i, + count = table->column_count, + sw_count = table->sw_column_count, + bs_count = table->bs_column_count; + + /* Root tables don't have columns */ + if (!count && !sw_count && !bs_count) { + result->columns = NULL; + return result; + } + + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + if (count && !sw_count) { + result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * count); + } else if (count || sw_count) { + /* If a table has switch columns, store vector with the case values + * as a regular column, so it's easier to access for systems. To + * enable this, we need to allocate more space. */ + int32_t type_count = ecs_vector_count(type); + result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * type_count); + } + + if (count) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Is the column a component? */ + const EcsComponent *component = flecs_component_from_id(world, e); + if (component) { + /* Is the component associated wit a (non-empty) type? */ + if (component->size) { + /* This is a regular component column */ + result->columns[i].size = flecs_to_i16(component->size); + result->columns[i].alignment = flecs_to_i16(component->alignment); + } else { + /* This is a tag */ + } + } else { + /* This is an entity that was added to the type */ + } + } + } + + if (sw_count) { + int32_t sw_offset = table->sw_column_offset; + result->sw_columns = ecs_os_calloc(ECS_SIZEOF(ecs_sw_column_t) * sw_count); + + for (i = 0; i < sw_count; i ++) { + ecs_entity_t e = entities[i + sw_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t sw_type = type_ptr->normalized; + + ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); + int32_t sw_array_count = ecs_vector_count(sw_type); + + ecs_switch_t *sw = flecs_switch_new( + sw_array[0], + sw_array[sw_array_count - 1], + 0); + result->sw_columns[i].data = sw; + result->sw_columns[i].type = sw_type; + } + } + + if (bs_count) { + result->bs_columns = ecs_os_calloc(ECS_SIZEOF(ecs_bs_column_t) * bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&result->bs_columns[i].data); + } + } + + return result; +} + +static +ecs_flags32_t get_component_action_flags( + const ecs_type_info_t *c_info) +{ + ecs_flags32_t flags = 0; + + if (c_info->lifecycle.ctor) { + flags |= EcsTableHasCtors; + } + if (c_info->lifecycle.dtor) { + flags |= EcsTableHasDtors; + } + if (c_info->lifecycle.copy) { + flags |= EcsTableHasCopy; + } + if (c_info->lifecycle.move) { + flags |= EcsTableHasMove; + } + + return flags; +} + +/* Check if table has instance of component, including pairs */ +static +bool has_component( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component) +{ + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + for (i = 0; i < count; i ++) { + if (component == ecs_get_typeid(world, entities[i])) { + return true; + } + } + + return false; +} + +static +void notify_component_info( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_type_t table_type = table->type; + if (!component || has_component(world, table_type, component)){ + int32_t column_count = ecs_vector_count(table_type); + ecs_assert(!component || column_count != 0, ECS_INTERNAL_ERROR, NULL); + + if (!column_count) { + return; + } + + if (!table->c_info) { + table->c_info = ecs_os_calloc( + ECS_SIZEOF(ecs_type_info_t*) * column_count); + } + + /* Reset lifecycle flags before recomputing */ + table->flags &= ~EcsTableHasLifecycle; + + /* Recompute lifecycle flags */ + ecs_entity_t *array = ecs_vector_first(table_type, ecs_entity_t); + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_entity_t c = ecs_get_typeid(world, array[i]); + if (!c) { + continue; + } + + const ecs_type_info_t *c_info = flecs_get_c_info(world, c); + if (c_info) { + ecs_flags32_t flags = get_component_action_flags(c_info); + table->flags |= flags; + } + + /* Store pointer to c_info for fast access */ + table->c_info[i] = (ecs_type_info_t*)c_info; + } + } +} + +static +void notify_trigger( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) +{ + (void)world; + + if (!(table->flags & EcsTableIsDisabled)) { + if (event == EcsOnAdd) { + table->flags |= EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + table->flags |= EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + table->flags |= EcsTableHasOnSet; + } else if (event == EcsUnSet) { + table->flags |= EcsTableHasUnSet; + } + } +} + +static +void run_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + int32_t count = ecs_vector_count(data->entities); + if (count) { + flecs_run_monitors(world, table, table->un_set_all, 0, count, NULL); + + int32_t i, type_count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + for (i = 0; i < type_count; i ++) { + ecs_ids_t removed = { + .array = &ids[i], + .count = 1 + }; + + flecs_run_remove_actions(world, table, data, 0, count, &removed); + } + } +} + +static +int compare_matched_query( + const void *ptr1, + const void *ptr2) +{ + const ecs_matched_query_t *m1 = ptr1; + const ecs_matched_query_t *m2 = ptr2; + ecs_query_t *q1 = m1->query; + ecs_query_t *q2 = m2->query; + ecs_assert(q1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(q2 != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t s1 = q1->system; + ecs_entity_t s2 = q2->system; + ecs_assert(s1 != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(s2 != 0, ECS_INTERNAL_ERROR, NULL); + + return (s1 > s2) - (s1 < s2); +} + +static +void add_monitor( + ecs_vector_t **array, + ecs_query_t *query, + int32_t matched_table_index) +{ + /* Add the system to a list that contains all OnSet systems matched with + * this table. This makes it easy to get the list of systems that need to be + * executed when all components are set, like when new_w_data is used */ + ecs_matched_query_t *m = ecs_vector_add(array, ecs_matched_query_t); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + m->query = query; + m->matched_table_index = matched_table_index; + + /* Sort the system list so that it is easy to get the difference OnSet + * OnSet systems between two tables. */ + qsort( + ecs_vector_first(*array, ecs_matched_query_t), + flecs_to_size_t(ecs_vector_count(*array)), + ECS_SIZEOF(ecs_matched_query_t), + compare_matched_query); +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of queries that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_monitor( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* First check if system is already registered as monitor. It is possible + * the query just wants to update the matched_table_index (for example, if + * query tables got reordered) */ + ecs_vector_each(table->monitors, ecs_matched_query_t, m, { + if (m->query == query) { + m->matched_table_index = matched_table_index; + return; + } + }); + + add_monitor(&table->monitors, query, matched_table_index); + +#ifndef NDEBUG + char *str = ecs_type_str(world, table->type); + ecs_trace_2("monitor #[green]%s#[reset] registered with table #[red]%s", + ecs_get_name(world, query->system), str); + ecs_os_free(str); +#endif +} + +static +bool is_override( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t comp) +{ + if (!(table->flags & EcsTableHasIsA)) { + return false; + } + + ecs_type_t type = table->type; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_RELATION(e, EcsIsA)) { + if (ecs_has_id(world, ECS_PAIR_OBJECT(e), comp)) { + return true; + } + } + } + + return false; +} + +static +void register_on_set( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + + if (table->column_count) { + if (!table->on_set) { + table->on_set = + ecs_os_calloc(ECS_SIZEOF(ecs_vector_t) * table->column_count); + } + + /* Get the matched table which holds the list of actual components */ + ecs_matched_table_t *matched_table = ecs_vector_get( + query->tables, ecs_matched_table_t, matched_table_index); + + /* Keep track of whether query matches overrides. When a component is + * removed, diffing these arrays between the source and detination + * tables gives the list of OnSet systems to run, after exposing the + * component that was overridden. */ + bool match_override = false; + + /* Add system to each matched column. This makes it easy to get the list + * of systems when setting a single component. */ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t oper = term->oper; + + if (!(subj->set.mask & EcsSelf) || !subj->entity || + subj->entity != EcsThis) + { + continue; + } + + if (oper != EcsAnd && oper != EcsOptional) { + continue; + } + + ecs_entity_t comp = matched_table->ids[i]; + int32_t index = ecs_type_index_of(table->type, 0, comp); + if (index == -1) { + continue; + } + + if (index >= table->column_count) { + continue; + } + + ecs_vector_t *set_c = table->on_set[index]; + ecs_matched_query_t *m = ecs_vector_add(&set_c, ecs_matched_query_t); + m->query = query; + m->matched_table_index = matched_table_index; + table->on_set[index] = set_c; + + match_override |= is_override(world, table, comp); + } + + if (match_override) { + add_monitor(&table->on_set_override, query, matched_table_index); + } + } + + add_monitor(&table->on_set_all, query, matched_table_index); +} + +static +void register_un_set( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + table->flags |= EcsTableHasUnSet; + add_monitor(&table->un_set_all, query, matched_table_index); +} + +/* -- Private functions -- */ + +/* If table goes from 0 to >0 entities or from >0 entities to 0 entities notify + * queries. This allows systems associated with queries to move inactive tables + * out of the main loop. */ +static +void table_activate( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + bool activate) +{ + if (query) { + flecs_query_notify(world, query, &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } else { + ecs_vector_t *queries = table->queries; + ecs_query_t **buffer = ecs_vector_first(queries, ecs_query_t*); + int32_t i, count = ecs_vector_count(queries); + + for (i = 0; i < count; i ++) { + flecs_query_notify(world, buffer[i], &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } + } +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of tables that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + /* Register system with the table */ + if (!(query->flags & EcsQueryNoActivation)) { +#ifndef NDEBUG + /* Sanity check if query has already been added */ + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + ecs_assert(*q != query, ECS_INTERNAL_ERROR, NULL); + } +#endif + + ecs_query_t **q = ecs_vector_add(&table->queries, ecs_query_t*); + if (q) *q = query; + + ecs_data_t *data = flecs_table_get_data(table); + if (data && ecs_vector_count(data->entities)) { + table_activate(world, table, query, true); + } + } + + /* Register the query as a monitor */ + if (query->flags & EcsQueryMonitor) { + table->flags |= EcsTableHasMonitors; + register_monitor(world, table, query, matched_table_index); + } + + /* Register the query as an on_set system */ + if (query->flags & EcsQueryOnSet) { + register_on_set(world, table, query, matched_table_index); + } + + /* Register the query as an un_set system */ + if (query->flags & EcsQueryUnSet) { + register_un_set(world, table, query, matched_table_index); + } +} + +/* This function is called when a query is unmatched with a table. This can + * happen for queries that have shared components expressions in their signature + * and those shared components changed (for example, a base removed a comp). */ +static +void unregister_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query) +{ + (void)world; + + if (!(query->flags & EcsQueryNoActivation)) { + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + if (*q == query) { + break; + } + } + + /* Query must have been registered with table */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); + + /* Remove query */ + ecs_vector_remove(table->queries, ecs_query_t*, i); + } +} + +ecs_data_t* flecs_table_get_data( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->data; +} + +ecs_data_t* flecs_table_get_or_create_data( + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_data_t *data = table->data; + if (!data) { + data = table->data = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + } + return data; +} + +static +void ctor_component( + ecs_world_t *world, + ecs_type_info_t * cdata, + ecs_column_t * column, + ecs_entity_t * entities, + int32_t row, + int32_t count) +{ + /* A new component is constructed */ + ecs_xtor_t ctor; + if (cdata && (ctor = cdata->lifecycle.ctor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; + + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + + ctor(world, cdata->component, entities, ptr, + flecs_to_size_t(size), count, ctx); + } +} + +static +void dtor_component( + ecs_world_t *world, + ecs_type_info_t * cdata, + ecs_column_t * column, + ecs_entity_t * entities, + int32_t row, + int32_t count) +{ + if (!count) { + return; + } + + /* An old component is destructed */ + ecs_xtor_t dtor; + if (cdata && (dtor = cdata->lifecycle.dtor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; + + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + dtor(world, cdata->component, &entities[row], ptr, + flecs_to_size_t(size), count, ctx); + } +} + +static +void dtor_all_components( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t row, + int32_t count, + bool update_entity_index, + bool is_delete) +{ + /* Can't delete and not update the entity index */ + ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t **records = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t i, c, column_count = table->column_count, end = row + count; + + (void)records; + + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Prevent the storage from getting modified while deleting */ + ecs_defer_begin(world); + + /* Throw up a lock just to be sure */ + table->lock = true; + + /* Iterate entities first, then components. This ensures that only one + * entity is invalidated at a time, which ensures that destructors can + * safely access other entities. */ + for (i = row; i < end; i ++) { + for (c = 0; c < column_count; c++) { + ecs_column_t *column = &data->columns[c]; + dtor_component(world, table->c_info[c], column, entities, i, 1); + } + + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + if (update_entity_index) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + + if (is_delete) { + ecs_eis_delete(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } else { + // If this is not a delete, clear the entity index record + ecs_record_t r = {NULL, 0}; + ecs_eis_set(world, e, &r); + } + } else { + /* This should only happen in rare cases, such as when the data + * cleaned up is not part of the world (like with snapshots) */ + } + } + + table->lock = false; + + ecs_defer_end(world); + + /* If table does not have destructors, just update entity index */ + } else if (update_entity_index) { + if (is_delete) { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + + ecs_eis_delete(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } else { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + ecs_record_t r = {NULL, 0}; + ecs_eis_set(world, e, &r); + } + } + } +} + +static +void fini_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + bool do_on_remove, + bool update_entity_index, + bool is_delete, + bool deactivate) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (!data) { + return; + } + + if (do_on_remove) { + run_on_remove(world, table, data); + } + + int32_t count = flecs_table_data_count(data); + if (count) { + dtor_all_components(world, table, data, 0, count, + update_entity_index, is_delete); + } + + /* Sanity check */ + ecs_assert(ecs_vector_count(data->record_ptrs) == + ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + + 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 || (ecs_vector_count(columns[c].data) == + ecs_vector_count(data->entities)), ECS_INTERNAL_ERROR, NULL); + + ecs_vector_free(columns[c].data); + } + ecs_os_free(columns); + data->columns = NULL; + } + + ecs_sw_column_t *sw_columns = data->sw_columns; + if (sw_columns) { + int32_t c, column_count = table->sw_column_count; + for (c = 0; c < column_count; c ++) { + flecs_switch_free(sw_columns[c].data); + } + ecs_os_free(sw_columns); + data->sw_columns = NULL; + } + + ecs_bs_column_t *bs_columns = data->bs_columns; + if (bs_columns) { + int32_t c, column_count = table->bs_column_count; + for (c = 0; c < column_count; c ++) { + flecs_bitset_deinit(&bs_columns[c].data); + } + ecs_os_free(bs_columns); + data->bs_columns = NULL; + } + + ecs_vector_free(data->entities); + ecs_vector_free(data->record_ptrs); + + data->entities = NULL; + data->record_ptrs = NULL; + + if (deactivate && count) { + table_activate(world, table, 0, false); + } +} + +/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + fini_data(world, table, data, false, false, false, false); +} + +/* Cleanup, no OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, flecs_table_get_data(table), + false, true, false, true); +} + +/* Cleanup, run OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, flecs_table_get_data(table), true, 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) +{ + fini_data(world, table, flecs_table_get_data(table), true, true, true, true); +} + +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all UnSet handlers, if any */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + ecs_data_t *data = flecs_table_get_data(table); + if (data) { + run_on_remove(world, table, data); + } +} + +/* Free table resources. */ +void flecs_table_free( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + (void)world; + + /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ + ecs_data_t *data = flecs_table_get_data(table); + fini_data(world, table, data, false, true, true, false); + + flecs_table_clear_edges(world, table); + + flecs_unregister_table(world, table); + + ecs_os_free(table->lo_edges); + ecs_map_free(table->hi_edges); + ecs_vector_free(table->queries); + ecs_os_free(table->dirty_state); + ecs_vector_free(table->monitors); + ecs_vector_free(table->on_set_all); + ecs_vector_free(table->on_set_override); + ecs_vector_free(table->un_set_all); + + if (table->c_info) { + ecs_os_free(table->c_info); + } + + if (table->on_set) { + int32_t i; + for (i = 0; i < table->column_count; i ++) { + ecs_vector_free(table->on_set[i]); + } + ecs_os_free(table->on_set); + } + + table->id = 0; + + ecs_os_free(table->data); +} + +/* Free table type. Do this separately from freeing the table as types can be + * in use by application destructors. */ +void flecs_table_free_type( + ecs_table_t *table) +{ + ecs_vector_free((ecs_vector_t*)table->type); +} + +/* 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, NULL); + + (void)world; + ecs_os_free(table->lo_edges); + ecs_map_free(table->hi_edges); + table->lo_edges = NULL; + table->hi_edges = NULL; +} + +static +void mark_table_dirty( + ecs_table_t *table, + int32_t index) +{ + if (table->dirty_state) { + table->dirty_state[index] ++; + } +} + +void flecs_table_mark_dirty( + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (table->dirty_state) { + int32_t index = ecs_type_index_of(table->type, 0, component); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + table->dirty_state[index] ++; + } +} + +static +void move_switch_columns( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->sw_column_count; + int32_t i_new = 0, new_column_count = new_table->sw_column_count; + + if (!old_column_count || !new_column_count) { + return; + } + + ecs_sw_column_t *old_columns = old_data->sw_columns; + ecs_sw_column_t *new_columns = new_data->sw_columns; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t offset_new = new_table->sw_column_offset; + int32_t offset_old = old_table->sw_column_offset; + + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; + + if (new_component == old_component) { + ecs_switch_t *old_switch = old_columns[i_old].data; + ecs_switch_t *new_switch = new_columns[i_new].data; + + flecs_switch_ensure(new_switch, new_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_switch_get(old_switch, old_index + i); + flecs_switch_set(new_switch, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +static +void move_bitset_columns( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->bs_column_count; + int32_t i_new = 0, new_column_count = new_table->bs_column_count; + + if (!old_column_count || !new_column_count) { + return; + } + + ecs_bs_column_t *old_columns = old_data->bs_columns; + ecs_bs_column_t *new_columns = new_data->bs_columns; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t offset_new = new_table->bs_column_offset; + int32_t offset_old = old_table->bs_column_offset; + + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; + + if (new_component == old_component) { + ecs_bitset_t *old_bs = &old_columns[i_old].data; + ecs_bitset_t *new_bs = &new_columns[i_new].data; + + flecs_bitset_ensure(new_bs, new_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(old_bs, old_index + i); + flecs_bitset_set(new_bs, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +static +void ensure_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t * column_count_out, + int32_t * sw_column_count_out, + int32_t * bs_column_count_out, + ecs_column_t ** columns_out, + ecs_sw_column_t ** sw_columns_out, + ecs_bs_column_t ** bs_columns_out) +{ + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + + /* It is possible that the table data was created without content. + * Now that data is going to be written to the table, initialize */ + if (column_count | sw_column_count | bs_column_count) { + columns = data->columns; + sw_columns = data->sw_columns; + bs_columns = data->bs_columns; + + if (!columns && !sw_columns && !bs_columns) { + flecs_init_data(world, table, data); + columns = data->columns; + sw_columns = data->sw_columns; + bs_columns = data->bs_columns; + + ecs_assert(sw_column_count == 0 || sw_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(bs_column_count == 0 || bs_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + *column_count_out = column_count; + *sw_column_count_out = sw_column_count; + *bs_column_count_out = bs_column_count; + *columns_out = columns; + *sw_columns_out = sw_columns; + *bs_columns_out = bs_columns; + } + + ecs_assert(!column_count || columns, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!sw_column_count || sw_columns, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!bs_column_count || bs_columns, ECS_INTERNAL_ERROR, NULL); +} + +static +void grow_column( + ecs_world_t *world, + ecs_entity_t * entities, + ecs_column_t * column, + ecs_type_info_t * c_info, + int32_t to_add, + int32_t new_size, + bool construct) +{ + ecs_vector_t *vec = column->data; + int16_t alignment = column->alignment; + + int32_t size = column->size; + int32_t count = ecs_vector_count(vec); + int32_t old_size = ecs_vector_size(vec); + int32_t new_count = count + to_add; + bool can_realloc = new_size != old_size; + + ecs_assert(new_size >= new_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; + if (c_info && count && can_realloc && (move = c_info->lifecycle.move)) { + ecs_xtor_t ctor = c_info->lifecycle.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Create new vector */ + ecs_vector_t *new_vec = ecs_vector_new_t(size, alignment, new_size); + ecs_vector_set_count_t(&new_vec, size, alignment, new_count); + + void *old_buffer = ecs_vector_first_t( + vec, size, alignment); + + void *new_buffer = ecs_vector_first_t( + new_vec, size, alignment); + + /* First construct elements (old and new) in new buffer */ + ctor(world, c_info->component, entities, new_buffer, + flecs_to_size_t(size), construct ? new_count : count, + c_info->lifecycle.ctx); + + /* Move old elements */ + move(world, c_info->component, entities, entities, + new_buffer, old_buffer, flecs_to_size_t(size), count, + c_info->lifecycle.ctx); + + /* Free old vector */ + ecs_vector_free(vec); + column->data = new_vec; + } else { + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_vector_set_size_t(&vec, size, alignment, new_size); + } + + void *elem = ecs_vector_addn_t(&vec, size, alignment, to_add); + + ecs_xtor_t ctor; + if (construct && c_info && (ctor = c_info->lifecycle.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(world, c_info->component, &entities[count], elem, + flecs_to_size_t(size), to_add, c_info->lifecycle.ctx); + } + + column->data = vec; + } + + ecs_assert(ecs_vector_size(column->data) == new_size, + ECS_INTERNAL_ERROR, NULL); +} + +static +int32_t grow_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur_count = flecs_table_data_count(data); + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + + /* Add record to record ptr array */ + ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); + ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_vector_size(data->record_ptrs) > size) { + size = ecs_vector_size(data->record_ptrs); + } + + /* Add entity to column with entity ids */ + ecs_vector_set_size(&data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); + + /* Initialize entity ids and record ptrs */ + int32_t i; + if (ids) { + for (i = 0; i < to_add; i ++) { + e[i] = ids[i]; + } + } else { + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } + ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); + + /* Add elements to each column array */ + ecs_type_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + if (!column->size) { + continue; + } + + ecs_type_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; + } + + grow_column(world, entities, column, c_info, to_add, size, true); + ecs_assert(ecs_vector_size(columns[i].data) == size, + ECS_INTERNAL_ERROR, NULL); + } + + /* Add elements to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_switch_t *sw = sw_columns[i].data; + flecs_switch_addn(sw, to_add); + } + + /* Add elements to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i].data; + flecs_bitset_addn(bs, to_add); + } + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + if (!world->is_readonly && !cur_count) { + table_activate(world, table, 0, true); + } + + table->alloc_count ++; + + /* Return index of first added entity */ + return cur_count; +} + +static +void fast_append( + ecs_column_t *columns, + int32_t column_count) +{ + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_add_t(&column->data, size, alignment); + } + } +} + +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + ecs_entity_t entity, + ecs_record_t * record, + bool construct) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + int32_t count = ecs_vector_count(data->entities); + int32_t size = ecs_vector_size(data->entities); + + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *e = entity; + + /* Keep track of alloc count. This allows references to check if cached + * pointers need to be updated. */ + table->alloc_count += (count == size); + + /* Add record ptr to array with record ptrs */ + ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + *r = record; + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + /* 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 (!world->is_readonly && !count) { + table_activate(world, table, 0, true); + } + + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + fast_append(columns, column_count); + return count; + } + + ecs_type_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + /* Reobtain size to ensure that the columns have the same size as the + * entities and record vectors. This keeps reasoning about when allocations + * occur easier. */ + size = ecs_vector_size(data->entities); + + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + if (!column->size) { + continue; + } + + ecs_type_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; + } + + grow_column(world, entities, column, c_info, 1, size, construct); + + ecs_assert( + ecs_vector_size(columns[i].data) == ecs_vector_size(data->entities), + ECS_INTERNAL_ERROR, NULL); + + ecs_assert( + ecs_vector_count(columns[i].data) == ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); + } + + /* Add element to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = sw_columns[i].data; + flecs_switch_add(sw); + columns[i + table->sw_column_offset].data = flecs_switch_values(sw); + } + + /* Add element to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i].data; + flecs_bitset_addn(bs, 1); + } + + return count; +} + +static +void fast_delete_last( + ecs_column_t *columns, + int32_t column_count) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vector_remove_last(column->data); + } +} + +static +void 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]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_remove_t(column->data, size, alignment, index); + } + } +} + +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t index, + bool destruct) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_vector_t *v_entities = data->entities; + int32_t count = ecs_vector_count(v_entities); + + 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 = ecs_vector_first(v_entities, ecs_entity_t); + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[index]; + entities[index] = entity_to_move; + ecs_vector_remove_last(v_entities); + + /* Move last record ptr to index */ + ecs_vector_t *v_records = data->record_ptrs; + ecs_assert(count < ecs_vector_count(v_records), ECS_INTERNAL_ERROR, NULL); + + ecs_record_t **records = ecs_vector_first(v_records, ecs_record_t*); + ecs_record_t *record_to_move = records[count]; + records[index] = record_to_move; + ecs_vector_remove_last(v_records); + + /* Update record of moved entity in entity index */ + if (index != count) { + if (record_to_move) { + if (record_to_move->row >= 0) { + record_to_move->row = index + 1; + } else { + record_to_move->row = -(index + 1); + } + 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 */ + mark_table_dirty(table, 0); + + /* If table is empty, deactivate it */ + if (!count) { + table_activate(world, table, NULL, false); + } + + /* Destruct component data */ + ecs_type_info_t **c_info_array = table->c_info; + 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) { + fast_delete_last(columns, column_count); + } else { + fast_delete(columns, column_count, index); + } + + return; + } + + /* Last element, destruct & remove */ + if (index == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + ecs_type_info_t *c_info = c_info_array[i]; + ecs_xtor_t dtor; + if (c_info && (dtor = c_info->lifecycle.dtor)) { + ecs_size_t size = c_info->size; + ecs_size_t alignment = c_info->alignment; + dtor(world, c_info->component, &entity_to_delete, + ecs_vector_last_t(columns[i].data, size, alignment), + flecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } + } + } + + fast_delete_last(columns, column_count); + + /* Not last element, move last element to deleted element & destruct */ + } else { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { + ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_size_t size = column->size; + ecs_size_t align = column->alignment; + ecs_vector_t *vec = column->data; + void *dst = ecs_vector_get_t(vec, size, align, index); + void *src = ecs_vector_last_t(vec, size, align); + + ecs_type_info_t *c_info = c_info_array[i]; + ecs_move_ctor_t move_dtor; + if (c_info && (move_dtor = c_info->lifecycle.move_dtor)) { + move_dtor(world, c_info->component, &c_info->lifecycle, + &entity_to_move, &entity_to_delete, dst, src, + flecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + + ecs_vector_remove_last(vec); + } + + } else { + fast_delete(columns, column_count, index); + } + } + + /* Remove elements from switch columns */ + ecs_sw_column_t *sw_columns = data->sw_columns; + int32_t sw_column_count = table->sw_column_count; + for (i = 0; i < sw_column_count; i ++) { + flecs_switch_remove(sw_columns[i].data, index); + } + + /* Remove elements from bitset columns */ + ecs_bs_column_t *bs_columns = data->bs_columns; + int32_t bs_column_count = table->bs_column_count; + for (i = 0; i < bs_column_count; i ++) { + flecs_bitset_remove(&bs_columns[i].data, index); + } +} + +static +void fast_move( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index) +{ + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t i_new = 0, new_column_count = new_table->column_count; + int32_t i_old = 0, old_column_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + + if (size) { + int16_t alignment = new_column->alignment; + void *dst = ecs_vector_get_t( + new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t( + old_column->data, size, alignment, old_index); + + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(dst, src, size); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + bool construct) +{ + ecs_assert(new_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_assert(old_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(old_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!((new_table->flags | old_table->flags) & EcsTableIsComplex)) { + fast_move(new_table, new_data, new_index, old_table, old_data, old_index); + return; + } + + move_switch_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); + + move_bitset_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); + + bool same_entity = dst_entity == src_entity; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t i_new = 0, new_column_count = new_table->column_count; + int32_t i_old = 0, old_column_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + int16_t alignment = new_column->alignment; + + if (size) { + void *dst = ecs_vector_get_t( + new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t( + old_column->data, size, alignment, old_index); + + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_info_t *cdata = new_table->c_info[i_new]; + if (same_entity) { + ecs_move_ctor_t callback; + if (cdata && (callback = cdata->lifecycle.ctor_move_dtor)) { + void *ctx = cdata->lifecycle.ctx; + /* ctor + move + dtor */ + callback(world, new_component, &cdata->lifecycle, + &dst_entity, &src_entity, + dst, src, flecs_to_size_t(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + ecs_copy_ctor_t copy; + if (cdata && (copy = cdata->lifecycle.copy_ctor)) { + void *ctx = cdata->lifecycle.ctx; + copy(world, new_component, &cdata->lifecycle, + &dst_entity, &src_entity, + dst, src, flecs_to_size_t(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } + } + } else { + if (new_component < old_component) { + if (construct) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } + } else { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } + + if (construct) { + for (; (i_new < new_column_count); i_new ++) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } + } + + for (; (i_old < old_column_count); i_old ++) { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } +} + +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t to_add, + const ecs_entity_t *ids) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + int32_t cur_count = flecs_table_data_count(data); + return grow_data(world, table, data, to_add, cur_count + to_add, ids); +} + +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t size) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + int32_t cur_count = flecs_table_data_count(data); + + if (cur_count < size) { + grow_data(world, table, data, 0, size, NULL); + } else if (!size) { + /* Initialize columns if 0 is passed. This is a shortcut to initialize + * columns when, for example, an API call is inserting bulk data. */ + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns; + ecs_sw_column_t *sw_columns; + ecs_bs_column_t *bs_columns; + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + } +} + +int32_t flecs_table_data_count( + const ecs_data_t *data) +{ + return data ? ecs_vector_count(data->entities) : 0; +} + +static +void swap_switch_columns( + ecs_table_t *table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->sw_column_count; + if (!column_count) { + return; + } + + ecs_sw_column_t *columns = data->sw_columns; + + for (i = 0; i < column_count; i ++) { + ecs_switch_t *sw = columns[i].data; + flecs_switch_swap(sw, row_1, row_2); + } +} + +static +void swap_bitset_columns( + ecs_table_t *table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->bs_column_count; + if (!column_count) { + return; + } + + ecs_bs_column_t *columns = data->bs_columns; + + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i].data; + flecs_bitset_swap(bs, row_1, row_2); + } +} + +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2) +{ + (void)world; + + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); + + if (row_1 == row_2) { + return; + } + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; + + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_record_t *record_ptr_1 = record_ptrs[row_1]; + ecs_record_t *record_ptr_2 = record_ptrs[row_2]; + + 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 */ + bool watched_1 = record_ptr_1->row < 0; + bool watched_2 = record_ptr_2->row < 0; + + /* Swap entities & records */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = flecs_row_to_record(row_2, watched_1); + record_ptr_2->row = flecs_row_to_record(row_1, watched_2); + record_ptrs[row_1] = record_ptr_2; + record_ptrs[row_2] = record_ptr_1; + + swap_switch_columns(table, data, row_1, row_2); + swap_bitset_columns(table, data, row_1, row_2); + + ecs_column_t *columns = data->columns; + if (!columns) { + return; + } + + /* Swap columns */ + int32_t i, column_count = table->column_count; + + for (i = 0; i < column_count; i ++) { + int16_t size = columns[i].size; + int16_t alignment = columns[i].alignment; + void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); + + if (size) { + void *tmp = ecs_os_alloca(size); + + void *el_1 = ECS_OFFSET(ptr, size * row_1); + void *el_2 = ECS_OFFSET(ptr, size * row_2); + + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } + } +} + +static +void merge_vector( + ecs_vector_t **dst_out, + ecs_vector_t *src, + int16_t size, + int16_t alignment) +{ + ecs_vector_t *dst = *dst_out; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); + } + + *dst_out = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); + + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); + + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + + ecs_vector_free(src); + *dst_out = dst; + } +} + +static +void merge_column( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t column_id, + ecs_vector_t *src) +{ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_type_info_t *c_info = table->c_info[column_id]; + ecs_column_t *column = &data->columns[column_id]; + ecs_vector_t *dst = column->data; + int16_t size = column->size; + int16_t alignment = column->alignment; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); + } + + column->data = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); + column->data = dst; + + /* Construct new values */ + if (c_info) { + ctor_component( + world, c_info, column, entities, dst_count, src_count); + } + + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); + + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + /* Move values into column */ + ecs_move_t move; + if (c_info && (move = c_info->lifecycle.move)) { + move(world, c_info->component, entities, entities, + dst_ptr, src_ptr, flecs_to_size_t(size), src_count, + c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + } + + ecs_vector_free(src); + } +} + +static +void merge_table_data( + ecs_world_t *world, + ecs_table_t * new_table, + ecs_table_t * old_table, + int32_t old_count, + int32_t new_count, + ecs_data_t * old_data, + ecs_data_t * new_data) +{ + int32_t i_new = 0, new_component_count = new_table->column_count; + int32_t i_old = 0, old_component_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_table->type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_table->type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + if (!new_columns && !new_data->entities) { + flecs_init_data(world, new_table, new_data); + new_columns = new_data->columns; + } + + ecs_assert(!new_component_count || new_columns, ECS_INTERNAL_ERROR, NULL); + + if (!old_count) { + return; + } + + /* Merge entities */ + merge_vector(&new_data->entities, old_data->entities, ECS_SIZEOF(ecs_entity_t), + ECS_ALIGNOF(ecs_entity_t)); + old_data->entities = NULL; + ecs_entity_t *entities = ecs_vector_first(new_data->entities, ecs_entity_t); + + ecs_assert(ecs_vector_count(new_data->entities) == old_count + new_count, + ECS_INTERNAL_ERROR, NULL); + + /* Merge entity index record pointers */ + merge_vector(&new_data->record_ptrs, old_data->record_ptrs, + ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); + old_data->record_ptrs = NULL; + + for (; (i_new < new_component_count) && (i_old < old_component_count); ) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + int16_t size = new_columns[i_new].size; + int16_t alignment = new_columns[i_new].alignment; + + if (new_component == old_component) { + merge_column(world, new_table, new_data, i_new, + old_columns[i_old].data); + old_columns[i_old].data = NULL; + + /* Mark component column as dirty */ + mark_table_dirty(new_table, i_new + 1); + + i_new ++; + i_old ++; + } else if (new_component < old_component) { + /* New column does not occur in old table, make sure vector is large + * enough. */ + if (size) { + ecs_column_t *column = &new_columns[i_new]; + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_type_info_t *c_info = new_table->c_info[i_new]; + if (c_info) { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + } + + i_new ++; + } else if (new_component > old_component) { + if (size) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_type_info_t *c_info = old_table->c_info[i_old]; + if (c_info) { + dtor_component(world, c_info, column, + entities, 0, old_count); + } + + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + } + + i_old ++; + } + } + + move_switch_columns( + new_table, new_data, new_count, old_table, old_data, 0, old_count); + + /* Initialize remaining columns */ + for (; i_new < new_component_count; i_new ++) { + ecs_column_t *column = &new_columns[i_new]; + int16_t size = column->size; + int16_t alignment = column->alignment; + + if (size) { + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_type_info_t *c_info = new_table->c_info[i_new]; + if (c_info) { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + } + } + + /* Destroy remaining columns */ + for (; i_old < old_component_count; i_old ++) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_type_info_t *c_info = old_table->c_info[i_old]; + if (c_info) { + dtor_component(world, c_info, column, entities, + 0, old_count); + } + + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + } + + /* Mark entity column as dirty */ + mark_table_dirty(new_table, 0); +} + +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = table->data; + if (!data) { + return 0; + } + + return flecs_table_data_count(data); +} + +ecs_data_t* flecs_table_merge( + ecs_world_t *world, + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data) +{ + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); + + bool move_data = false; + + /* If there is nothing to merge to, just clear the old table */ + if (!new_table) { + flecs_table_clear_data(world, old_table, old_data); + return NULL; + } else { + ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); + } + + /* If there is no data to merge, drop out */ + if (!old_data) { + return NULL; + } + + if (!new_data) { + new_data = flecs_table_get_or_create_data(new_table); + if (new_table == old_table) { + move_data = true; + } + } + + ecs_entity_t *old_entities = ecs_vector_first(old_data->entities, ecs_entity_t); + int32_t old_count = ecs_vector_count(old_data->entities); + int32_t new_count = ecs_vector_count(new_data->entities); + + ecs_record_t **old_records = ecs_vector_first( + old_data->record_ptrs, ecs_record_t*); + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < old_count; i ++) { + ecs_record_t *record; + if (new_table != old_table) { + record = old_records[i]; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + record = ecs_eis_ensure(world, old_entities[i]); + } + + bool is_monitored = record->row < 0; + record->row = flecs_row_to_record(new_count + i, is_monitored); + record->table = new_table; + } + + /* Merge table columns */ + if (move_data) { + *new_data = *old_data; + } else { + merge_table_data(world, new_table, old_table, old_count, new_count, + old_data, new_data); + } + + new_table->alloc_count ++; + + if (!new_count && old_count) { + table_activate(world, new_table, NULL, true); + } + + return new_data; +} + +void flecs_table_replace_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data) +{ + int32_t prev_count = 0; + ecs_data_t *table_data = table->data; + ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (table_data) { + prev_count = ecs_vector_count(table_data->entities); + run_on_remove(world, table, table_data); + flecs_table_clear_data(world, table, table_data); + } + + if (data) { + table_data = flecs_table_get_or_create_data(table); + *table_data = *data; + } else { + return; + } + + int32_t count = ecs_table_count(table); + + if (!prev_count && count) { + table_activate(world, table, 0, true); + } else if (prev_count && !count) { + table_activate(world, table, 0, false); + } +} + +bool flecs_table_match_filter( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_filter_t * filter) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!filter) { + return true; + } + + ecs_type_t type = table->type; + + if (filter->include) { + /* If filter kind is exact, types must be the same */ + if (filter->include_kind == EcsMatchExact) { + if (type != filter->include) { + return false; + } + + /* Default for include_kind is MatchAll */ + } else if (!flecs_type_contains(world, type, filter->include, + filter->include_kind != EcsMatchAny, true)) + { + return false; + } + } + + if (filter->exclude) { + /* If filter kind is exact, types must be the same */ + if (filter->exclude_kind == EcsMatchExact) { + if (type == filter->exclude) { + return false; + } + + /* Default for exclude_kind is MatchAny */ + } else if (flecs_type_contains(world, type, filter->exclude, + filter->exclude_kind == EcsMatchAll, true)) + { + return false; + } + } + + return true; +} + +int32_t* flecs_table_get_dirty_state( + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (!table->dirty_state) { + table->dirty_state = ecs_os_calloc(ECS_SIZEOF(int32_t) * (table->column_count + 1)); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + } + return table->dirty_state; +} + +int32_t* flecs_table_get_monitor( + ecs_table_t *table) +{ + int32_t *dirty_state = flecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t column_count = table->column_count; + return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); +} + +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t * event) +{ + if (world->is_fini) { + return; + } + + switch(event->kind) { + case EcsTableQueryMatch: + register_query( + world, table, event->query, event->matched_table_index); + break; + case EcsTableQueryUnmatch: + unregister_query( + world, table, event->query); + break; + case EcsTableComponentInfo: + notify_component_info(world, table, event->component); + break; + case EcsTableTriggerMatch: + notify_trigger(world, table, event->event); + break; + } +} + +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (world->magic == ECS_WORLD_MAGIC && !world->is_readonly) { + table->lock ++; + } +} + +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (world->magic == ECS_WORLD_MAGIC && !world->is_readonly) { + table->lock --; + ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); + } +} + +bool ecs_table_has_module( + ecs_table_t *table) +{ + return table->flags & EcsTableHasModule; +} + +ecs_column_t *ecs_table_column_for_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + if (tr) { + ecs_data_t *data = table->data; + if (data) { + return &data->columns[tr->column]; + } + } + + return NULL; +} + + +static +const ecs_entity_t* new_w_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *component_ids, + int32_t count, + void **c_info, + int32_t *row_out); + +static +void* get_component_w_index( + ecs_table_t *table, + int32_t column_index, + int32_t row) +{ + ecs_assert(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); + + ecs_data_t *data = flecs_table_get_data(table); + ecs_column_t *column = &data->columns[column_index]; + + /* If size is 0, component does not have a value. This is likely caused by + * an application trying to call ecs_get with a tag. */ + int32_t size = column->size; + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + + void *ptr = ecs_vector_first_t(column->data, size, column->alignment); + return ECS_OFFSET(ptr, size * row); +} + +static +void* get_component( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + ecs_table_record_t *tr = ecs_map_get(idr->table_index, + ecs_table_record_t, table->id); + if (!tr) { + return NULL; + } + + return get_component_w_index(table, tr->column, row); +} + +static +void* get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_map_t *table_index, + ecs_map_t *table_index_isa, + int32_t recur_depth) +{ + /* Cycle detected in IsA relation */ + ecs_assert(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); + + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { + return NULL; + } + + /* Exclude Name */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { + return NULL; + } + + /* Should always be an id record for IsA, otherwise a table with a + * HasBase flag set should not exist. */ + if (!table_index_isa) { + ecs_id_record_t *idr = flecs_get_id_record(world, ecs_pair(EcsIsA, EcsWildcard)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + table_index_isa = idr->table_index; + } + + /* 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 = ecs_map_get( + table_index_isa, ecs_table_record_t, table->id); + ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t type = table->type; + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; + void *ptr = NULL; + + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_object(world, pair); + + ecs_record_t *r = ecs_eis_get(world, base); + if (!r) { + continue; + } + + table = r->table; + if (!table) { + continue; + } + + ecs_table_record_t *tr = ecs_map_get(table_index, + ecs_table_record_t, table->id); + if (!tr) { + ptr = get_base_component(world, table, id, table_index, + table_index_isa, recur_depth + 1); + } else { + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + ptr = get_component_w_index(table, tr->column, row); + } + } while (!ptr && (i < end)); + + return ptr; +} + +/* Utility to compute actual row from row in record */ +static +int32_t set_row_info( + ecs_entity_info_t *info, + int32_t row) +{ + return info->row = flecs_record_to_row(row, &info->is_watched); +} + +/* Utility to set info from main stage record */ +static +void set_info_from_record( + ecs_entity_info_t * info, + ecs_record_t * record) +{ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + + info->record = record; + + ecs_table_t *table = record->table; + + set_row_info(info, record->row); + + info->table = table; + if (!info->table) { + return; + } + + ecs_data_t *data = flecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + info->data = data; + + ecs_assert(ecs_vector_count(data->entities) > info->row, + ECS_INTERNAL_ERROR, NULL); +} + +static +const ecs_type_info_t *get_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + return flecs_get_c_info(world, real_id); + } else { + return NULL; + } +} + +static +int get_column_info( + ecs_world_t * world, + ecs_table_t * table, + ecs_ids_t * components, + ecs_column_info_t * cinfo, + bool get_all) +{ + int32_t column_count = table->column_count; + ecs_entity_t *type_array = ecs_vector_first(table->type, ecs_entity_t); + + if (get_all) { + int32_t i, count = ecs_vector_count(table->type); + for (i = 0; i < count; i ++) { + ecs_entity_t id = type_array[i]; + cinfo[i].id = id; + cinfo[i].ci = get_c_info(world, id); + cinfo[i].column = i; + } + + return count; + } else { + ecs_entity_t *array = components->array; + int32_t i, cur, count = components->count; + for (i = 0; i < count; i ++) { + ecs_entity_t id = array[i]; + cinfo[i].id = id; + cinfo[i].ci = get_c_info(world, id); + cinfo[i].column = -1; + + for (cur = 0; cur < column_count; cur ++) { + if (type_array[cur] == id) { + cinfo[i].column = cur; + break; + } + } + } + + return count; + } +} + +#ifdef FLECS_SYSTEM +static +void run_set_systems_for_entities( + ecs_world_t * world, + ecs_ids_t * components, + ecs_table_t * table, + int32_t row, + int32_t count, + ecs_entity_t * entities, + bool set_all) +{ + if (set_all) { + /* Run OnSet systems for all components of the entity. This usually + * happens when an entity is created directly in its target table. */ + ecs_vector_t *queries = table->on_set_all; + ecs_vector_each(queries, ecs_matched_query_t, m, { + flecs_run_monitor(world, m, components, row, count, entities); + }); + } else { + /* Run OnSet systems for a specific component. This usually happens when + * an application calls ecs_set or ecs_modified. The entity's table + * stores a vector for each component with the OnSet systems for that + * component. This vector maintains the same order as the table's type, + * which makes finding the correct set of systems as simple as getting + * the index of a component id in the table type. + * + * One thing to note is that the system may be invoked for a table that + * is not the same as the entity for which the system is invoked. This + * can happen in the case of instancing, where adding an IsA + * relationship conceptually adds components to an entity, but the + * actual components are stored on the base entity. */ + ecs_vector_t **on_set_systems = table->on_set; + if (on_set_systems) { + int32_t index = ecs_type_index_of(table->type, 0, components->array[0]); + + /* This should never happen, as an OnSet system should only ever be + * invoked for entities that have the component for which this + * function was invoked. */ + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *queries = on_set_systems[index]; + ecs_vector_each(queries, ecs_matched_query_t, m, { + flecs_run_monitor(world, m, components, row, count, entities); + }); + } + } +} +#endif + +static +void notify( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_entity_t event, + ecs_ids_t *ids) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t *arr = ids->array; + int32_t arr_count = ids->count; + + int i; + for (i = 0; i < arr_count; i ++) { + flecs_triggers_notify(world, arr[i], event, table, data, row, count); + } +} + +static +void instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count); + +static +void instantiate_children( + ecs_world_t * world, + ecs_entity_t base, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_table_t * child_table) +{ + ecs_type_t type = child_table->type; + ecs_data_t *child_data = flecs_table_get_data(child_table); + if (!child_data || !flecs_table_data_count(child_data)) { + return; + } + + int32_t column_count = child_table->column_count; + ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); + int32_t type_count = ecs_vector_count(type); + + /* Instantiate child table for each instance */ + + /* Create component array for creating the table */ + ecs_ids_t components = { + .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) + }; + + void **c_info = ecs_os_alloca_n(void*, column_count); + + /* 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 i, base_index = -1, pos = 0; + + for (i = 0; i < type_count; i ++) { + ecs_entity_t c = type_array[i]; + + /* Make sure instances don't have EcsPrefab */ + if (c == EcsPrefab) { + 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(c, EcsChildOf) && (ecs_entity_t_lo(c) == base)) { + base_index = pos; + } + + /* Store pointer to component array. We'll use this component array to + * create our new entities in bulk with new_w_data */ + if (i < column_count) { + ecs_column_t *column = &child_data->columns[i]; + c_info[pos] = ecs_vector_first_t( + column->data, column->size, column->alignment); + } else if (pos < column_count) { + c_info[pos] = NULL; + } + + components.array[pos] = c; + pos ++; + } + + ecs_assert(base_index != -1, ECS_INTERNAL_ERROR, NULL); + + /* If children are added to a prefab, make sure they are prefabs too */ + if (table->flags & EcsTableIsPrefab) { + components.array[pos] = EcsPrefab; + pos ++; + } + + components.count = pos; + + /* Instantiate the prefab child table for each new instance */ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t child_count = ecs_vector_count(child_data->entities); + + for (i = row; i < count + row; i ++) { + ecs_entity_t instance = entities[i]; + + /* Replace ChildOf element in the component array with instance id */ + components.array[base_index] = ecs_pair(EcsChildOf, instance); + + /* Find or create table */ + ecs_table_t *i_table = flecs_table_find_or_create(world, &components); + ecs_assert(i_table != NULL, 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. */ + int j; + ecs_entity_t *children = ecs_vector_first( + child_data->entities, ecs_entity_t); +#ifndef NDEBUG + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_assert(child != instance, ECS_INVALID_PARAMETER, NULL); + } +#endif + + /* Create children */ + int32_t child_row; + new_w_data(world, i_table, NULL, child_count, c_info, &child_row); + + /* If prefab child table has children itself, recursively instantiate */ + ecs_data_t *i_data = flecs_table_get_data(i_table); + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + instantiate(world, child, i_table, i_data, child_row + j, 1); + } + } +} + +static +void instantiate( + ecs_world_t * world, + ecs_entity_t base, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count) +{ + /* If base is a parent, instantiate children of base for instances */ + const ecs_id_record_t *r = flecs_get_id_record( + world, ecs_pair(EcsChildOf, base)); + + if (r && r->table_index) { + ecs_table_record_t *tr; + ecs_map_iter_t it = ecs_map_iter(r->table_index); + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + instantiate_children( + world, base, table, data, row, count, tr->table); + } + } +} + +static +bool override_component( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_t type, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count); + +static +bool override_from_base( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t component, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_info_t base_info; + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + if (!flecs_get_info(world, base, &base_info) || !base_info.table) { + return false; + } + + void *base_ptr = get_component( + world, base_info.table, base_info.row, component); + if (base_ptr) { + int16_t data_size = column->size; + void *data_array = ecs_vector_first_t( + column->data, column->size, column->alignment); + void *data_ptr = ECS_OFFSET(data_array, data_size * row); + + component = ecs_get_typeid(world, component); + const ecs_type_info_t *cdata = flecs_get_c_info(world, component); + int32_t index; + + ecs_copy_t copy = cdata ? cdata->lifecycle.copy : NULL; + if (copy) { + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + void *ctx = cdata->lifecycle.ctx; + for (index = 0; index < count; index ++) { + copy(world, component, &entities[row], &base, + data_ptr, base_ptr, flecs_to_size_t(data_size), 1, ctx); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } + } else { + for (index = 0; index < count; index ++) { + ecs_os_memcpy(data_ptr, base_ptr, data_size); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } + } + + return true; + } else { + /* If component not found on base, check if base itself inherits */ + ecs_type_t base_type = base_info.table->type; + return override_component(world, component, base_type, data, column, + row, count); + } +} + +static +bool override_component( + ecs_world_t * world, + ecs_entity_t component, + ecs_type_t type, + ecs_data_t * data, + ecs_column_t * column, + int32_t row, + int32_t count) +{ + ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); + int32_t i, type_count = ecs_vector_count(type); + + /* Walk prefabs */ + i = type_count - 1; + do { + ecs_entity_t e = type_array[i]; + + if (!(e & ECS_ROLE_MASK)) { + break; + } + + if (ECS_HAS_RELATION(e, EcsIsA)) { + if (override_from_base(world, ecs_pair_object(world, e), component, + data, column, row, count)) + { + return true; + } + } + } while (--i >= 0); + + return false; +} + +static +void components_override( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_column_info_t * component_info, + int32_t component_count, + bool run_on_set) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component_info != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table_without_base = table; + ecs_column_t *columns = data->columns; + ecs_type_t type = table->type; + int32_t column_count = table->column_count; + + int i; + for (i = 0; i < component_count; i ++) { + ecs_entity_t component = component_info[i].id; + + if (component >= ECS_HI_COMPONENT_ID) { + if (ECS_HAS_RELATION(component, EcsIsA)) { + ecs_entity_t base = ECS_PAIR_OBJECT(component); + + /* Illegal to create an instance of 0 */ + ecs_assert(base != 0, ECS_INVALID_PARAMETER, NULL); + instantiate(world, base, table, data, row, count); + + /* If table has on_set systems, get table without the base + * entity that was just added. This is needed to determine the + * diff between the on_set systems of the current table and the + * table without the base, as these are the systems that need to + * be invoked */ + ecs_ids_t to_remove = { + .array = &component, + .count = 1 + }; + + table_without_base = flecs_table_traverse_remove(world, + table_without_base, &to_remove, NULL); + } + } + + int32_t column_index = component_info[i].column; + if (column_index == -1 || column_index >= column_count) { + continue; + } + + /* column_index is lower than column count, which means we must have + * data columns */ + ecs_assert(data->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!data->columns[column_index].size) { + continue; + } + + ecs_column_t *column = &columns[column_index]; + if (override_component(world, component, type, data, column, + row, count)) + { + ecs_ids_t to_remove = { + .array = &component, + .count = 1 + }; + table_without_base = flecs_table_traverse_remove(world, + table_without_base, &to_remove, NULL); + } + } + + /* Run OnSet actions when a base entity is added to the entity for + * components not overridden by the entity. */ + if (run_on_set && table_without_base != table) { + flecs_run_monitors(world, table, table->on_set_all, row, count, + table_without_base->on_set_all); + } +} + +static +void set_switch( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_ids_t *entities, + bool reset) +{ + ecs_entity_t *array = entities->array; + int32_t i, comp_count = entities->count; + + for (i = 0; i < comp_count; i ++) { + ecs_entity_t e = array[i]; + + if (ECS_HAS_ROLE(e, CASE)) { + e = e & ECS_COMPONENT_MASK; + + ecs_entity_t sw_case = 0; + if (!reset) { + sw_case = e; + ecs_assert(sw_case != 0, ECS_INTERNAL_ERROR, NULL); + } + + int32_t sw_index = flecs_table_switch_from_case(world, table, e); + ecs_assert(sw_index != -1, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = data->sw_columns[sw_index].data; + ecs_assert(sw != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t r; + for (r = 0; r < count; r ++) { + flecs_switch_set(sw, row + r, sw_case); + } + } + } +} + +static +void ecs_components_switch( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_ids_t *added, + ecs_ids_t *removed) +{ + if (added) { + set_switch(world, table, data, row, count, added, false); + } + if (removed) { + set_switch(world, table, data, row, count, removed, true); + } +} + +static +int32_t new_entity( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * new_table, + ecs_ids_t * added, + bool construct) +{ + ecs_record_t *record = info->record; + ecs_data_t *new_data = flecs_table_get_or_create_data(new_table); + int32_t new_row; + + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!record) { + record = ecs_eis_ensure(world, entity); + } + + new_row = flecs_table_append( + world, new_table, new_data, entity, record, construct); + + record->table = new_table; + record->row = flecs_row_to_record(new_row, info->is_watched); + + ecs_assert( + ecs_vector_count(new_data[0].entities) > new_row, + ECS_INTERNAL_ERROR, NULL); + + if (new_table->flags & EcsTableHasAddActions) { + flecs_run_add_actions( + world, new_table, new_data, new_row, 1, added, true, true); + + if (new_table->flags & EcsTableHasMonitors) { + flecs_run_monitors( + world, new_table, new_table->monitors, new_row, 1, NULL); + } + } + + info->data = new_data; + + return new_row; +} + +static +int32_t move_entity( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * src_table, + ecs_data_t * src_data, + int32_t src_row, + ecs_table_t * dst_table, + ecs_ids_t * added, + ecs_ids_t * removed, + bool construct) +{ + ecs_data_t *dst_data = flecs_table_get_or_create_data(dst_table); + ecs_assert(src_data != dst_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = info->record; + ecs_assert(!record || record == ecs_eis_get(world, entity), + ECS_INTERNAL_ERROR, NULL); + + int32_t dst_row = flecs_table_append(world, dst_table, dst_data, entity, + record, false); + + ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + + /* Copy entity & components from src_table to dst_table */ + if (src_table->type) { + /* If components were removed, invoke remove actions before deleting */ + if (removed && (src_table->flags & EcsTableHasRemoveActions)) { + /* If entity was moved, invoke UnSet monitors for each component that + * the entity no longer has */ + flecs_run_monitors(world, dst_table, src_table->un_set_all, + src_row, 1, dst_table->un_set_all); + + flecs_run_remove_actions( + world, src_table, src_data, src_row, 1, removed); + } + + flecs_table_move(world, entity, entity, dst_table, dst_data, dst_row, + src_table, src_data, src_row, construct); + } + + /* Update entity index & delete old data after running remove actions */ + record->table = dst_table; + record->row = flecs_row_to_record(dst_row, info->is_watched); + + flecs_table_delete(world, src_table, src_data, src_row, false); + + /* If components were added, invoke add actions */ + if (src_table != dst_table || (added && added->count)) { + if (added && (dst_table->flags & EcsTableHasAddActions)) { + flecs_run_add_actions( + world, dst_table, dst_data, dst_row, 1, added, false, true); + } + + /* Run monitors */ + if (dst_table->flags & EcsTableHasMonitors) { + flecs_run_monitors(world, dst_table, dst_table->monitors, dst_row, + 1, src_table->monitors); + } + + /* If removed components were overrides, run OnSet systems for those, as + * the value of those components changed from the removed component to + * the value of component on the base entity */ + if (removed && dst_table->flags & EcsTableHasIsA) { + flecs_run_monitors(world, dst_table, src_table->on_set_override, + dst_row, 1, dst_table->on_set_override); + } + } + + info->data = dst_data; + + return dst_row; +} + +static +void delete_entity( + ecs_world_t * world, + ecs_table_t * src_table, + ecs_data_t * src_data, + int32_t src_row, + ecs_ids_t * removed) +{ + if (removed) { + flecs_run_monitors(world, src_table, src_table->un_set_all, + src_row, 1, NULL); + + /* Invoke remove actions before deleting */ + if (src_table->flags & EcsTableHasRemoveActions) { + flecs_run_remove_actions( + world, src_table, src_data, src_row, 1, removed); + } + } + + flecs_table_delete(world, src_table, src_data, src_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 update_component_monitor_w_array( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t relation, + ecs_ids_t * entities) +{ + if (!entities) { + return; + } + + int i; + for (i = 0; i < entities->count; i ++) { + ecs_entity_t id = entities->array[i]; + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(id); + + /* If a relationship has changed, check if it could have impacted + * the shape of the graph for that relationship. If so, mark the + * relationship as dirty */ + if (rel != relation && flecs_get_id_record(world, ecs_pair(rel, entity))) { + update_component_monitor_w_array(world, entity, rel, entities); + } + + } + + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* If an IsA relationship is added to a monitored entity (can + * be either a parent or a base) component monitors need to be + * evaluated for the components of the prefab. */ + ecs_entity_t base = ecs_pair_object(world, id); + ecs_type_t type = ecs_get_type(world, base); + ecs_ids_t base_entities = flecs_type_to_ids(type); + + /* This evaluates the component monitor for all components of the + * base entity. If the base entity contains IsA relationships + * these will be evaluated recursively as well. */ + update_component_monitor_w_array( + world, entity, relation, &base_entities); + } else { + flecs_monitor_mark_dirty(world, relation, id); + } + } +} + +static +void update_component_monitors( + ecs_world_t * world, + ecs_entity_t entity, + ecs_ids_t * added, + ecs_ids_t * removed) +{ + update_component_monitor_w_array(world, entity, 0, added); + update_component_monitor_w_array(world, entity, 0, removed); +} + +static +void commit( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * dst_table, + ecs_ids_t * added, + ecs_ids_t * removed, + bool construct) +{ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = info->table; + if (src_table == dst_table) { + /* If source and destination table are the same no action is needed * + * However, if a component was added in the process of traversing a + * table, this suggests that a case switch could have occured. */ + if (((added && added->count) || (removed && removed->count)) && + src_table && src_table->flags & EcsTableHasSwitch) + { + ecs_components_switch( + world, src_table, info->data, info->row, 1, added, removed); + } + + return; + } + + if (src_table) { + ecs_data_t *src_data = info->data; + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (dst_table->type) { + info->row = move_entity(world, entity, info, src_table, + src_data, info->row, dst_table, added, removed, construct); + info->table = dst_table; + } else { + delete_entity(world, src_table, src_data, info->row, removed); + + ecs_eis_set(world, entity, &(ecs_record_t){ + NULL, (info->is_watched == true) * -1 + }); + } + } else { + if (dst_table->type) { + info->row = new_entity( + world, entity, info, dst_table, added, construct); + info->table = dst_table; + } + } + + /* If the entity is being watched, it is being monitored for changes and + * requires rematching systems when components are added or removed. This + * ensures that systems that rely on components from containers or prefabs + * update the matched tables when the application adds or removes a + * component from, for example, a container. */ + if (info->is_watched) { + update_component_monitors(world, entity, added, removed); + } + + if ((!src_table || !src_table->type) && world->range_check_enabled) { + ecs_assert(!world->stats.max_id || entity <= world->stats.max_id, ECS_OUT_OF_RANGE, 0); + ecs_assert(entity >= world->stats.min_id, ECS_OUT_OF_RANGE, 0); + } +} + +static +void new( + ecs_world_t * world, + ecs_entity_t entity, + ecs_ids_t * to_add) +{ + ecs_entity_info_t info = {0}; + ecs_table_t *table = flecs_table_traverse_add( + world, &world->store.root, to_add, NULL); + new_entity(world, entity, &info, table, to_add, true); +} + +static +const ecs_entity_t* new_w_data( + ecs_world_t * world, + ecs_table_t * table, + ecs_ids_t * component_ids, + int32_t count, + void ** component_data, + int32_t * row_out) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + int32_t sparse_count = ecs_eis_count(world); + const ecs_entity_t *ids = flecs_sparse_new_ids(world->store.entity_index, count); + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t type = table->type; + + if (!type) { + return ids; + } + + ecs_ids_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = ecs_vector_first(type, ecs_entity_t); + component_array.count = ecs_vector_count(type); + } + + ecs_data_t *data = flecs_table_get_or_create_data(table); + int32_t row = flecs_table_appendn(world, table, data, count, ids); + ecs_ids_t added = flecs_type_to_ids(type); + + /* Update entity index. */ + int i; + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + for (i = 0; i < count; i ++) { + record_ptrs[row + i] = ecs_eis_set(world, ids[i], + &(ecs_record_t){ + .table = table, + .row = row + i + 1 + }); + } + + flecs_defer_none(world, &world->stage); + + flecs_run_add_actions(world, table, data, row, count, &added, + true, component_data == NULL); + + if (component_data) { + /* Set components that we're setting in the component mask so the init + * actions won't call OnSet triggers for them. This ensures we won't + * call OnSet triggers multiple times for the same component */ + int32_t c_i; + for (c_i = 0; c_i < component_ids->count; c_i ++) { + ecs_entity_t c = component_ids->array[c_i]; + + /* Bulk copy column data into new table */ + int32_t table_index = ecs_type_index_of(type, 0, c); + ecs_assert(table_index >= 0, ECS_INTERNAL_ERROR, NULL); + if (table_index >= table->column_count) { + continue; + } + + ecs_column_t *column = &data->columns[table_index]; + int16_t size = column->size; + if (!size) { + continue; + } + + int16_t alignment = column->alignment; + void *ptr = ecs_vector_first_t(column->data, size, alignment); + ptr = ECS_OFFSET(ptr, size * row); + + /* Copy component data */ + void *src_ptr = component_data[c_i]; + if (!src_ptr) { + continue; + } + + const ecs_type_info_t *cdata = get_c_info(world, c); + ecs_copy_t copy; + if (cdata && (copy = cdata->lifecycle.copy)) { + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + copy(world, c, entities, entities, ptr, src_ptr, + flecs_to_size_t(size), count, cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, src_ptr, size * count); + } + }; + + flecs_run_set_systems(world, 0, table, data, NULL, row, count, true); + } + + flecs_run_monitors(world, table, table->monitors, row, count, NULL); + + flecs_defer_flush(world, &world->stage); + + if (row_out) { + *row_out = row; + } + + ids = flecs_sparse_ids(world->store.entity_index); + + return &ids[sparse_count]; +} + +static +bool has_type( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type, + bool match_any, + bool match_prefabs) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return false; + } + + if (!type) { + return true; + } + + ecs_type_t entity_type = ecs_get_type(world, entity); + + return flecs_type_contains( + world, entity_type, type, match_any, match_prefabs) != 0; +} + +static +void add_remove( + ecs_world_t * world, + ecs_entity_t entity, + ecs_ids_t * to_add, + ecs_ids_t * to_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + ecs_assert(to_add->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t add_buffer[ECS_MAX_ADD_REMOVE]; + ecs_entity_t remove_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = add_buffer }; + ecs_ids_t removed = { .array = remove_buffer }; + + ecs_table_t *src_table = info.table; + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, to_remove, &removed); + + dst_table = flecs_table_traverse_add( + world, dst_table, to_add, &added); + + commit(world, entity, &info, dst_table, &added, &removed, true); +} + +static +void add_ids_w_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_ids_t * components, + bool construct) +{ + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = buffer }; + + ecs_table_t *src_table = info->table; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, components, &added); + + commit(world, entity, info, dst_table, &added, NULL, construct); +} + +static +void remove_ids_w_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_ids_t * components) +{ + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = buffer }; + + ecs_table_t *src_table = info->table; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, components, &removed); + + commit(world, entity, info, dst_table, NULL, &removed, true); +} + +static +void add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_ids_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_add(world, stage, entity, components)) { + return; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = buffer }; + + ecs_table_t *src_table = info.table; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, components, &added); + + commit(world, entity, &info, dst_table, &added, NULL, true); + + flecs_defer_flush(world, stage); +} + +static +void remove_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_ids_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_remove(world, stage, entity, components)) { + return; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = buffer }; + + ecs_table_t *src_table = info.table; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, components, &removed); + + commit(world, entity, &info, dst_table, NULL, &removed, true); + + flecs_defer_flush(world, stage); +} + +static +void *get_mutable( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_t component, + ecs_entity_info_t * info, + bool * is_added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert((component & ECS_COMPONENT_MASK) == component || + ECS_HAS_ROLE(component, PAIR), ECS_INVALID_PARAMETER, NULL); + + void *dst = NULL; + if (flecs_get_info(world, entity, info) && info->table) { + dst = get_component(world, info->table, info->row, component); + } + + if (!dst) { + ecs_table_t *table = info->table; + + ecs_ids_t to_add = { + .array = &component, + .count = 1 + }; + + add_ids_w_info(world, entity, info, &to_add, true); + + flecs_get_info(world, entity, info); + ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL); + + dst = get_component(world, info->table, info->row, component); + + if (is_added) { + *is_added = table != info->table; + } + + return dst; + } else { + if (is_added) { + *is_added = false; + } + + return dst; + } +} + + +/* -- Private functions -- */ + +void flecs_run_add_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_ids_t * added, + bool get_all, + bool run_on_set) +{ + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (table->flags & EcsTableHasIsA) { + ecs_column_info_t cinfo[ECS_MAX_ADD_REMOVE]; + + int added_count = get_column_info( + world, table, added, cinfo, get_all); + + components_override( + world, table, data, row, count, cinfo, + added_count, run_on_set); + } + + if (table->flags & EcsTableHasSwitch) { + ecs_components_switch(world, table, data, row, count, added, NULL); + } + + if (table->flags & EcsTableHasOnAdd) { + notify(world, table, data, row, count, EcsOnAdd, added); + } +} + +void flecs_run_remove_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_ids_t * removed) +{ + ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); + + if (count) { + if (table->flags & EcsTableHasUnSet) { + notify(world, table, data, row, count, EcsUnSet, removed); + } + if (table->flags & EcsTableHasOnRemove) { + notify(world, table, data, row, count, EcsOnRemove, removed); + } + } +} + +bool flecs_get_info( + const ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info) +{ + info->table = NULL; + info->record = NULL; + info->data = NULL; + info->is_watched = false; + + if (entity & ECS_ROLE) { + return false; + } + + ecs_record_t *record = ecs_eis_get(world, entity); + + if (!record) { + return false; + } + + set_info_from_record(info, record); + + return true; +} + +void flecs_run_set_systems( + ecs_world_t *world, + ecs_id_t component, + ecs_table_t *table, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count, + bool set_all) +{ + ecs_assert(set_all || column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!column || column->size != 0, ECS_INTERNAL_ERROR, NULL); + + if (!count || !data || (column && !column->size)) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row); + + ecs_ids_t components; + + if (!set_all) { + const ecs_type_info_t *info = get_c_info(world, component); + ecs_on_set_t on_set; + if (info && (on_set = info->lifecycle.on_set)) { + ecs_size_t size = column->size; + void *ptr = ecs_vector_get_t( + column->data, size, column->alignment, row); + on_set(world, component, entities, ptr, flecs_to_size_t(size), count, + info->lifecycle.ctx); + } + + components = (ecs_ids_t){ + .array = &component, + .count = component != 0 + }; + } else { + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t i, column_count = table->column_count; + + for (i = 0; i < column_count; i ++) { + ecs_id_t id = ids[i]; + const ecs_type_info_t *info = get_c_info(world, id); + ecs_on_set_t on_set; + if (info && (on_set = info->lifecycle.on_set)) { + ecs_column_t *c = &data->columns[i]; + ecs_size_t size = c->size; + if (!size) { + continue; + } + + void *ptr = ecs_vector_get_t(c->data, size, c->alignment, row); + on_set(world, ids[i], entities, ptr, flecs_to_size_t(size), + count, info->lifecycle.ctx); + } + } + + components = (ecs_ids_t){ + .array = ids, + .count = column_count + }; + } + +#ifdef FLECS_SYSTEM + run_set_systems_for_entities(world, &components, table, row, + count, entities, set_all); +#endif + + if (table->flags & EcsTableHasOnSet) { + if (!set_all) { + notify(world, table, data, row, count, EcsOnSet, &components); + } else { + int32_t i, column_count = table->column_count; + for (i = 0; i < column_count; i ++) { + ecs_column_t *c = &data->columns[i]; + if (c->size) { + notify(world, table, data, row, count, EcsOnSet, + &components); + } + } + } + } +} + +void flecs_run_monitors( + ecs_world_t * world, + ecs_table_t * dst_table, + ecs_vector_t * v_dst_monitors, + int32_t dst_row, + int32_t count, + ecs_vector_t *v_src_monitors) +{ + (void)world; + (void)dst_table; + (void)v_dst_monitors; + (void)dst_row; + (void)count; + (void)v_src_monitors; + +#ifdef FLECS_SYSTEM + if (v_dst_monitors == v_src_monitors) { + return; + } + + if (!v_dst_monitors) { + return; + } + + ecs_assert(!(dst_table->flags & EcsTableIsPrefab), ECS_INTERNAL_ERROR, NULL); + + if (!v_src_monitors) { + ecs_vector_each(v_dst_monitors, ecs_matched_query_t, monitor, { + flecs_run_monitor(world, monitor, NULL, dst_row, count, NULL); + }); + } else { + /* If both tables have monitors, run the ones that dst_table has and + * src_table doesn't have */ + int32_t i, m_count = ecs_vector_count(v_dst_monitors); + int32_t j = 0, src_count = ecs_vector_count(v_src_monitors); + ecs_matched_query_t *dst_monitors = ecs_vector_first(v_dst_monitors, ecs_matched_query_t); + ecs_matched_query_t *src_monitors = ecs_vector_first(v_src_monitors, ecs_matched_query_t); + + for (i = 0; i < m_count; i ++) { + ecs_matched_query_t *dst = &dst_monitors[i]; + + ecs_entity_t system = dst->query->system; + ecs_assert(system != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_matched_query_t *src = 0; + while (j < src_count) { + src = &src_monitors[j]; + if (src->query->system < system) { + j ++; + } else { + break; + } + } + + if (src && src->query->system == system) { + continue; + } + + flecs_run_monitor(world, dst, NULL, dst_row, count, NULL); + } + } +#endif +} + +int32_t flecs_record_to_row( + int32_t row, + bool *is_watched_out) +{ + bool is_watched = row < 0; + row = row * -(is_watched * 2 - 1) - 1 * (row != 0); + *is_watched_out = is_watched; + return row; +} + +int32_t flecs_row_to_record( + int32_t row, + bool is_watched) +{ + return (row + 1) * -(is_watched * 2 - 1); +} + +ecs_ids_t flecs_type_to_ids( + ecs_type_t type) +{ + return (ecs_ids_t){ + .array = ecs_vector_first(type, ecs_entity_t), + .count = ecs_vector_count(type) + }; +} + +void flecs_set_watch( + ecs_world_t *world, + ecs_entity_t entity) +{ + (void)world; + + ecs_record_t *record = ecs_eis_get(world, entity); + if (!record) { + ecs_record_t new_record = {.row = -1, .table = NULL}; + ecs_eis_set(world, entity, &new_record); + } else { + if (record->row > 0) { + record->row *= -1; + + } else if (record->row == 0) { + /* If entity is empty, there is no index to change the sign of. In + * this case, set the index to -1, and assign an empty type. */ + record->row = -1; + record->table = NULL; + } + } +} + + +/* -- Public functions -- */ + +bool ecs_commit( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + ecs_ids_t *added, + ecs_ids_t *removed) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = NULL; + if (!record) { + record = ecs_eis_get(world, entity); + src_table = record->table; + } + + ecs_entity_info_t info = {0}; + if (record) { + set_info_from_record(&info, record); + } + + commit(world, entity, &info, table, added, removed, true); + + return src_table != table; +} + +ecs_entity_t ecs_new_id( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * since it is thread safe (uses atomic inc when in threading mode) */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_entity_t entity; + + int32_t stage_count = ecs_get_stage_count(unsafe_world); + if (stage->asynchronous || (ecs_os_has_threading() && stage_count > 1)) { + /* Can't atomically increase number above max int */ + ecs_assert( + unsafe_world->stats.last_id < UINT_MAX, ECS_INTERNAL_ERROR, NULL); + + entity = (ecs_entity_t)ecs_os_ainc( + (int32_t*)&unsafe_world->stats.last_id); + } else { + entity = ecs_eis_recycle(unsafe_world); + } + + ecs_assert(!unsafe_world->stats.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->stats.max_id, + ECS_OUT_OF_RANGE, NULL); + + return entity; +} + +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_id_t prev = stage->with; + stage->with = id; + return prev; +} + +ecs_entity_t ecs_get_with( + const ecs_world_t *world) +{ + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->with; +} + +ecs_entity_t ecs_new_component_id( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, 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_world_t*)ecs_get_world(world); + + if (unsafe_world->is_readonly) { + /* Can't issue new comp id while iterating when in multithreaded mode */ + ecs_assert(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_ITERATING, NULL); + } + + ecs_entity_t id; + + if (unsafe_world->stats.last_component_id < ECS_HI_COMPONENT_ID) { + do { + id = unsafe_world->stats.last_component_id ++; + } while (ecs_exists(unsafe_world, id) && id < ECS_HI_COMPONENT_ID); + } + + if (unsafe_world->stats.last_component_id >= ECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + id = ecs_new_id(unsafe_world); + } + + return id; +} + +ecs_entity_t ecs_new_w_type( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + if (!type) { + return ecs_new_w_id(world, 0); + } + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + ecs_id_t with = stage->with; + ecs_entity_t scope = stage->scope; + + ecs_ids_t to_add = flecs_type_to_ids(type); + if (flecs_defer_new(world, stage, entity, &to_add)) { + if (with) { + ecs_add_id(world, entity, with); + } + if (scope) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } + return entity; + } + + new(world, entity, &to_add); + + ecs_id_t ids[2]; + to_add = (ecs_ids_t){ .array = ids, .count = 0 }; + + if (with) { + ids[to_add.count ++] = with; + } + + if (scope) { + ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); + } + + if (to_add.count) { + add_ids(world, entity, &to_add); + } + + flecs_defer_flush(world, stage); + + return entity; +} + +ecs_entity_t ecs_new_w_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + + ecs_id_t ids[3]; + ecs_ids_t to_add = { .array = ids, .count = 0 }; + + if (id) { + ids[to_add.count ++] = id; + } + + ecs_id_t with = stage->with; + if (with) { + ids[to_add.count ++] = with; + } + + ecs_entity_t scope = stage->scope; + if (scope) { + if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { + ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); + } + } + + if (flecs_defer_new(world, stage, entity, &to_add)) { + return entity; + } + + if (to_add.count) { + new(world, entity, &to_add); + } else { + ecs_eis_set(world, entity, &(ecs_record_t){ 0 }); + } + + flecs_defer_flush(world, stage); + + return entity; +} + +#ifdef FLECS_PARSER + +/* Traverse table graph by either adding or removing identifiers parsed from the + * passed in expression. */ +static +ecs_table_t *traverse_from_expr( + ecs_world_t *world, + ecs_table_t *table, + const char *name, + const char *expr, + ecs_ids_t *modified, + bool is_add, + bool replace_and) +{ + int32_t size = modified->count; + if (size < ECS_MAX_ADD_REMOVE) { + size = ECS_MAX_ADD_REMOVE; + } + + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (ecs_term_finalize(world, name, expr, &term)) { + return NULL; + } + + if (!ecs_term_is_trivial(&term)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid non-trivial term in add expression"); + return NULL; + } + + if (modified->count == size) { + size *= 2; + ecs_id_t *arr = ecs_os_malloc(size * ECS_SIZEOF(ecs_id_t)); + ecs_os_memcpy(arr, modified->array, + modified->count * ECS_SIZEOF(ecs_id_t)); + + if (modified->count != ECS_MAX_ADD_REMOVE) { + ecs_os_free(modified->array); + } + + modified->array = arr; + } + + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + ecs_ids_t arr = { .array = &term.id, .count = 1 }; + if (is_add) { + table = flecs_table_traverse_add( + world, table, &arr, modified); + } else { + table = flecs_table_traverse_remove( + world, table, &arr, modified); + } + + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } else if (term.oper == EcsAndFrom) { + /* Add all components from the specified type */ + const EcsType *t = ecs_get(world, term.id, EcsType); + if (!t) { + ecs_parser_error(name, expr, (ptr - expr), + "expected type for AND role"); + return NULL; + } + + ecs_id_t *ids = ecs_vector_first(t->normalized, ecs_id_t); + int32_t i, count = ecs_vector_count(t->normalized); + for (i = 0; i < count; i ++) { + ecs_ids_t arr = { .array = &ids[i], .count = 1 }; + if (is_add) { + table = flecs_table_traverse_add( + world, table, &arr, modified); + } else { + table = flecs_table_traverse_remove( + world, table, &arr, modified); + } + + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + } + + ecs_term_fini(&term); + } + } + + return table; +} + +/* Add/remove components based on the parsed expression. This operation is + * slower than traverse_from_expr, but safe to use from a deferred context. */ +static +void defer_from_expr( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const char *expr, + bool is_add, + bool replace_and) +{ + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (ecs_term_finalize(world, name, expr, &term)) { + return; + } + + if (!ecs_term_is_trivial(&term)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid non-trivial term in add expression"); + return; + } + + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + if (is_add) { + ecs_add_id(world, entity, term.id); + } else { + ecs_remove_id(world, entity, term.id); + } + } else if (term.oper == EcsAndFrom) { + /* Add all components from the specified type */ + const EcsType *t = ecs_get(world, term.id, EcsType); + if (!t) { + ecs_parser_error(name, expr, (ptr - expr), + "expected type for AND role"); + return; + } + + ecs_id_t *ids = ecs_vector_first(t->normalized, ecs_id_t); + int32_t i, count = ecs_vector_count(t->normalized); + for (i = 0; i < count; i ++) { + if (is_add) { + ecs_add_id(world, entity, ids[i]); + } else { + ecs_remove_id(world, entity, ids[i]); + } + } + } + + ecs_term_fini(&term); + } + } +} +#endif + +/* If operation is not deferred, add/remove components by finding the target + * table and moving the entity towards it. */ +static +void traverse_add_remove( + 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; + + /* Find existing table */ + ecs_entity_info_t info = {0}; + ecs_table_t *src_table = NULL, *table = NULL; + if (!new_entity) { + if (flecs_get_info(world, result, &info)) { + table = info.table; + } + } + + ecs_entity_t added_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = added_buffer }; + + ecs_entity_t removed_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = removed_buffer }; + + /* 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) { + ecs_entity_t id = ecs_pair(EcsChildOf, scope); + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + if (with) { + ecs_ids_t arr = { .array = &with, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + } + + /* If a name is provided but not yet assigned, add the Name component */ + if (name && !name_assigned) { + ecs_entity_t id = ecs_pair(ecs_id(EcsIdentifier), EcsName); + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* Add components from the 'remove' id array */ + i = 0; + ids = desc->remove; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_remove(world, table, &arr, &removed); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { +#ifdef FLECS_PARSER + table = traverse_from_expr( + world, table, name, desc->add_expr, &added, true, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Remove components from the 'remove_expr' expression */ + if (desc->remove_expr) { +#ifdef FLECS_PARSER + table = traverse_from_expr( + world, table, name, desc->remove_expr, &removed, false, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Commit entity to destination table */ + if (src_table != table) { + commit(world, result, &info, table, &added, &removed, true); + } + + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, result, scope, name, sep, NULL); + ecs_assert(ecs_get_name(world, result) != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + if (desc->symbol) { + 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 (added.count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(added.array); + } + if (removed.count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(removed.array); + } +} + +/* When in deferred mode, we need to add/remove components one by one using + * the regular operations. */ +static +void deferred_add_remove( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) +{ + const char *sep = desc->sep; + + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (new_entity) { + if (new_entity && scope && !name && !name_assigned) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } + + if (with) { + ecs_add_id(world, entity, with); + } + } + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_add_id(world, entity, id); + } + + /* Add components from the 'remove' id array */ + i = 0; + ids = desc->remove; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_remove_id(world, entity, id); + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { +#ifdef FLECS_PARSER + defer_from_expr(world, entity, name, desc->add_expr, true, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Remove components from the 'remove_expr' expression */ + if (desc->remove_expr) { +#ifdef FLECS_PARSER + defer_from_expr(world, entity, name, desc->remove_expr, true, false); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, entity, scope, name, sep, NULL); + } + + /* Currently it's not supported to set the symbol from a deferred context */ + if (desc->symbol) { + const char *sym = ecs_get_symbol(world, entity); + ecs_assert(!ecs_os_strcmp(sym, desc->symbol), ECS_UNSUPPORTED, NULL); + (void)sym; + } +} + +ecs_entity_t ecs_entity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t scope = ecs_get_scope(world); + ecs_id_t with = ecs_get_with(world); + + const char *name = desc->name; + const char *sep = desc->sep; + if (!sep) { + sep = "."; + } + + const char *root_sep = desc->root_sep; + + bool 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->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; + } + } + } + + /* Find or create entity */ + ecs_entity_t result = desc->entity; + if (!result) { + if (name) { + 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_component_id(world); + } else { + result = ecs_new_id(world); + } + new_entity = true; + ecs_assert(ecs_get_type(world, result) == NULL, + ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_assert(ecs_is_valid(world, result), ECS_INVALID_PARAMETER, NULL); + + name_assigned = ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName); + if (name && name_assigned) { + /* If entity has name, verify that name matches */ + char *path = ecs_get_path_w_sep(world, scope, result, sep, NULL); + if (path) { + if (ecs_os_strcmp(path, name)) { + /* Mismatching name */ + ecs_os_free(path); + return 0; + } + ecs_os_free(path); + } + } + } + + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); + + if (stage->defer) { + deferred_add_remove(world, result, name, desc, + scope, with, new_entity, name_assigned); + } else { + traverse_add_remove(world, result, name, desc, + scope, with, new_entity, name_assigned); + } + + return result; +} + +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = (ecs_world_t*)ecs_get_world(world); + + bool is_readonly = world->is_readonly; + bool is_deferred = ecs_is_deferred(world); + int32_t defer_count = 0; + ecs_vector_t *defer_queue = NULL; + ecs_stage_t *stage = NULL; + + /* If world is readonly or deferring is enabled, component registration can + * still happen directly on the main storage, but only if the application + * is singlethreaded. */ + if (is_readonly || is_deferred) { + ecs_assert(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_ITERATING, NULL); + + /* Silence readonly warnings */ + world->is_readonly = false; + + /* Hack around safety checks (this ought to look ugly) */ + ecs_world_t *temp_world = world; + stage = flecs_stage_from_world(&temp_world); + defer_count = stage->defer; + defer_queue = stage->defer_queue; + stage->defer = 0; + stage->defer_queue = NULL; + } + + ecs_entity_desc_t entity_desc = desc->entity; + entity_desc.use_low_id = true; + if (!entity_desc.symbol) { + entity_desc.symbol = entity_desc.name; + } + + ecs_entity_t e = desc->entity.entity; + ecs_entity_t result = ecs_entity_init(world, &entity_desc); + if (!result) { + return 0; + } + + bool added = false; + EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent, &added); + + if (added) { + ptr->size = flecs_from_size_t(desc->size); + ptr->alignment = flecs_from_size_t(desc->alignment); + } else { + if (ptr->size != flecs_from_size_t(desc->size)) { + ecs_abort(ECS_INVALID_COMPONENT_SIZE, desc->entity.name); + } + if (ptr->alignment != flecs_from_size_t(desc->alignment)) { + ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, desc->entity.name); + } + } + + ecs_modified(world, result, EcsComponent); + + if (e > world->stats.last_component_id && e < ECS_HI_COMPONENT_ID) { + world->stats.last_component_id = e + 1; + } + + /* Ensure components cannot be deleted */ + ecs_add_pair(world, result, EcsOnDelete, EcsThrow); + + if (is_readonly || is_deferred) { + /* Restore readonly state / defer count */ + world->is_readonly = is_readonly; + stage->defer = defer_count; + stage->defer_queue = defer_queue; + } + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + + return result; +} + +ecs_entity_t ecs_type_init( + ecs_world_t *world, + const ecs_type_desc_t *desc) +{ + ecs_entity_t result = ecs_entity_init(world, &desc->entity); + if (!result) { + return 0; + } + + ecs_table_t *table = NULL, *normalized = NULL; + + ecs_entity_t added_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = added_buffer }; + + /* Find destination table (and type) */ + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->ids; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_ids_t arr = { .array = &id, .count = 1 }; + normalized = flecs_table_traverse_add(world, normalized, &arr, &added); + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* If expression is set, add it to the table */ + if (desc->ids_expr) { +#ifdef FLECS_PARSER + normalized = traverse_from_expr( + world, normalized, desc->entity.name, desc->ids_expr, &added, + true, true); + + table = traverse_from_expr( + world, table, desc->entity.name, desc->ids_expr, &added, + true, false); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + if (added.count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(added.array); + } + + ecs_type_t type = NULL; + ecs_type_t normalized_type = NULL; + + if (table) { + type = table->type; + } + if (normalized) { + normalized_type = normalized->type; + } + + bool add = false; + EcsType *type_ptr = ecs_get_mut(world, result, EcsType, &add); + if (add) { + type_ptr->type = type; + type_ptr->normalized = normalized_type; + + /* This will allow the type to show up in debug tools */ + if (type) { + ecs_map_set(world->type_handles, (uintptr_t)type, &result); + } + + ecs_modified(world, result, EcsType); + } else { + if (type_ptr->type != type) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + if (type_ptr->normalized != normalized_type) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } + + return result; +} + +const ecs_entity_t* ecs_bulk_new_w_data( + ecs_world_t *world, + int32_t count, + const ecs_ids_t *components, + void * data) +{ + ecs_assert(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, components, data, &ids)) { + return ids; + } + + ecs_table_t *table = flecs_table_find_or_create(world, components); + ids = new_w_data(world, table, NULL, count, data, NULL); + flecs_defer_flush(world, stage); + return ids; +} + +const ecs_entity_t* ecs_bulk_new_w_type( + ecs_world_t *world, + ecs_type_t type, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + const ecs_entity_t *ids; + ecs_ids_t components = flecs_type_to_ids(type); + if (flecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) { + return ids; + } + ecs_table_t *table = ecs_table_from_type(world, type); + ids = new_w_data(world, table, NULL, count, NULL, NULL); + flecs_defer_flush(world, stage); + return ids; +} + +const ecs_entity_t* ecs_bulk_new_w_id( + ecs_world_t *world, + ecs_id_t id, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_ids_t components = { + .array = &id, + .count = 1 + }; + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) { + return ids; + } + ecs_table_t *table = flecs_table_find_or_create(world, &components); + ids = new_w_data(world, table, NULL, count, NULL, NULL); + flecs_defer_flush(world, stage); + return ids; +} + +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(world, stage, entity)) { + return; + } + + ecs_entity_info_t info; + info.table = NULL; + + flecs_get_info(world, entity, &info); + + ecs_table_t *table = info.table; + if (table) { + ecs_type_t type = table->type; + + /* Remove all components */ + ecs_ids_t to_remove = flecs_type_to_ids(type); + remove_ids_w_info(world, entity, &info, &to_remove); + } + + flecs_defer_flush(world, stage); +} + +static +void on_delete_action( + ecs_world_t *world, + ecs_entity_t entity); + +static +void throw_invalid_delete( + ecs_world_t *world, + ecs_id_t id) +{ + char buff[256]; + ecs_id_str(world, id, buff, 256); + ecs_abort(ECS_INVALID_DELETE, buff); +} + +static +void remove_from_table( + ecs_world_t *world, + ecs_table_t *src_table, + ecs_id_t id, + int32_t column, + int32_t column_count) +{ + ecs_entity_t removed_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = removed_buffer }; + + if (column_count > ECS_MAX_ADD_REMOVE) { + removed.array = ecs_os_malloc_n(ecs_id_t, column_count); + } + + ecs_table_t *dst_table = src_table; + ecs_id_t *ids = ecs_vector_first(src_table->type, ecs_id_t); + + /* If id is pair but the column pointed to is not a pair, the record is + * pointing to an instance of the id that has a (non-PAIR) role. */ + bool is_pair = ECS_HAS_ROLE(id, PAIR); + bool is_role = is_pair && !ECS_HAS_ROLE(ids[column], PAIR); + ecs_assert(!is_role || ((ids[column] & ECS_ROLE_MASK) != 0), + ECS_INTERNAL_ERROR, NULL); + bool is_wildcard = ecs_id_is_wildcard(id); + + int32_t i, count = ecs_vector_count(src_table->type), removed_count = 0; + ecs_entity_t entity = ECS_PAIR_RELATION(id); + + for (i = column; i < count; i ++) { + ecs_id_t e = ids[i]; + + if (is_role) { + if ((e & ECS_COMPONENT_MASK) != entity) { + continue; + } + } else if (is_wildcard && !ecs_id_match(e, id)) { + continue; + } + + ecs_ids_t to_remove = { .array = &e, .count = 1 }; + dst_table = flecs_table_traverse_remove( + world, dst_table, &to_remove, &removed); + + removed_count ++; + if (removed_count == column_count) { + break; + } + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!dst_table->type) { + /* If this removes all components, clear table */ + flecs_table_clear_entities(world, src_table); + } else { + /* Otherwise, merge table into dst_table */ + if (dst_table != src_table) { + ecs_data_t *src_data = flecs_table_get_data(src_table); + int32_t src_count = ecs_table_count(src_table); + if (removed.count && src_data) { + flecs_run_remove_actions(world, src_table, + src_data, 0, src_count, &removed); + } + + ecs_data_t *dst_data = flecs_table_get_data(dst_table); + flecs_table_merge(world, dst_table, src_table, dst_data, src_data); + } + } + + if (column_count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(removed.array); + } +} + +static +void delete_objects( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_data_t *data = flecs_table_get_data(table); + if (data) { + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + int32_t i, count = ecs_vector_count(data->entities); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *r = flecs_sparse_get( + world->store.entity_index, ecs_record_t, e); + + /* If row is negative, it means the entity is being monitored. Only + * monitored entities can have delete actions */ + if (r && r->row < 0) { + /* Make row positive which prevents infinite recursion in case + * of cyclic delete actions */ + r->row = (-r->row); + + /* Run delete actions for objects */ + on_delete_action(world, entities[i]); + } + } + + /* Clear components from table (invokes destructors, OnRemove) */ + flecs_table_delete_entities(world, table); + } +} + +static +void delete_tables_for_id_record( + ecs_world_t *world, + ecs_id_t id, + ecs_id_record_t *idr) +{ + /* Delete tables in id record. Because deleting the table updates the + * map, remove the map pointer from the id record. This will prevent the + * table from removing itself from the map as it is deleted, which + * allows for iterating the map without changing it. */ + + if (!world->is_fini) { + ecs_map_t *table_index = idr->table_index; + idr->table_index = NULL; + ecs_map_iter_t it = ecs_map_iter(table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + flecs_delete_table(world, tr->table); + } + ecs_map_free(table_index); + + flecs_clear_id_record(world, id); + } +} + +static +void on_delete_object_action( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (idr) { + ecs_map_t *table_index = idr->table_index; + ecs_map_iter_t it = ecs_map_iter(table_index); + ecs_table_record_t *tr; + + /* Execute the on delete action */ + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + ecs_table_t *table = tr->table; + + if (!ecs_table_count(table)) { + continue; + } + + ecs_id_t *rel_id = ecs_vector_get(table->type, ecs_id_t, tr->column); + ecs_assert(rel_id != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t rel = ECS_PAIR_RELATION(*rel_id); + /* delete_object_action should be invoked for relations */ + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + + /* Get the record for the relation, to find the delete action */ + ecs_id_record_t *idrr = flecs_get_id_record(world, rel); + if (idrr) { + ecs_entity_t action = idrr->on_delete_object; + if (!action || action == EcsRemove) { + remove_from_table(world, table, id, tr->column, tr->count); + it = ecs_map_iter(table_index); + } else if (action == EcsDelete) { + delete_objects(world, table); + } else if (action == EcsThrow) { + throw_invalid_delete(world, id); + } + } else { + /* If no record was found for the relation, assume the default + * action which is to remove the relationship */ + remove_from_table(world, table, id, tr->column, tr->count); + it = ecs_map_iter(table_index); + } + } + + delete_tables_for_id_record(world, id, idr); + } +} + +static +void on_delete_relation_action( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + + char buf[255]; ecs_id_str(world, id, buf, 255); + + if (idr) { + ecs_entity_t on_delete = idr->on_delete; + if (on_delete == EcsThrow) { + throw_invalid_delete(world, id); + } + + ecs_map_t *table_index = idr->table_index; + ecs_map_iter_t it = ecs_map_iter(table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + ecs_table_t *table = tr->table; + ecs_entity_t action = idr->on_delete; + + if (!action || action == EcsRemove) { + remove_from_table(world, table, id, tr->column, tr->count); + } else if (action == EcsDelete) { + delete_objects(world, table); + } + } + + delete_tables_for_id_record(world, id, idr); + } +} + +static +void on_delete_action( + ecs_world_t *world, + ecs_entity_t entity) +{ + on_delete_relation_action(world, entity); + on_delete_relation_action(world, ecs_pair(entity, EcsWildcard)); + on_delete_object_action(world, ecs_pair(EcsWildcard, entity)); +} + +void ecs_delete_children( + ecs_world_t *world, + ecs_entity_t parent) +{ + on_delete_action(world, parent); +} + +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(world, stage, entity)) { + return; + } + + ecs_record_t *r = flecs_sparse_get( + world->store.entity_index, ecs_record_t, entity); + if (r) { + ecs_entity_info_t info = {0}; + set_info_from_record(&info, r); + + ecs_table_t *table = info.table; + uint64_t table_id = 0; + if (table) { + table_id = table->id; + } + + if (info.is_watched) { + /* Make row positive which prevents infinite recursion in case + * of cyclic delete actions */ + r->row = (-r->row); + + /* Ensure that the store contains no dangling references to the + * deleted entity (as a component, or as part of a relation) */ + on_delete_action(world, entity); + + /* Refetch data. In case of circular relations, the entity may have + * moved to a different table. */ + set_info_from_record(&info, r); + + table = info.table; + if (table) { + table_id = table->id; + } else { + table_id = 0; + } + + if (r->table) { + ecs_ids_t to_remove = flecs_type_to_ids(r->table->type); + update_component_monitors(world, entity, NULL, &to_remove); + } + } + + ecs_assert(!table_id || table, ECS_INTERNAL_ERROR, NULL); + + /* If entity has components, remove them. Check if table is still alive, + * as delete actions could have deleted the table already. */ + if (table_id && flecs_sparse_is_alive(world->store.tables, table_id)) { + ecs_type_t type = table->type; + ecs_ids_t to_remove = flecs_type_to_ids(type); + delete_entity(world, table, info.data, info.row, &to_remove); + r->table = NULL; + } + + r->row = 0; + + /* Remove (and invalidate) entity after executing handlers */ + flecs_sparse_remove(world->store.entity_index, entity); + } + + flecs_defer_flush(world, stage); +} + +void ecs_add_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = flecs_type_to_ids(type); + add_ids(world, entity, &components); +} + +void ecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = { .array = &id, .count = 1 }; + add_ids(world, entity, &components); +} + +void ecs_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = flecs_type_to_ids(type); + remove_ids(world, entity, &components); +} + +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = { .array = &id, .count = 1 }; + remove_ids(world, entity, &components); +} + +// DEPRECATED +void ecs_add_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id_add, + ecs_id_t id_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id_add), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id_remove), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components_add = { .array = &id_add, .count = 1 }; + ecs_ids_t components_remove = { .array = &id_remove, .count = 1 }; + add_remove(world, entity, &components_add, &components_remove); +} + +void ecs_add_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t to_add, + ecs_type_t to_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components_add = flecs_type_to_ids(to_add); + ecs_ids_t components_remove = flecs_type_to_ids(to_remove); + add_remove(world, entity, &components_add, &components_remove); +} + +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(src != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, src), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (!dst) { + dst = ecs_new_id(world); + } + + if (flecs_defer_clone(world, stage, dst, src, copy_value)) { + return dst; + } + + ecs_entity_info_t src_info; + bool found = flecs_get_info(world, src, &src_info); + ecs_table_t *src_table = src_info.table; + + if (!found || !src_table) { + return dst; + } + + ecs_type_t src_type = src_table->type; + ecs_ids_t to_add = flecs_type_to_ids(src_type); + + ecs_entity_info_t dst_info = {0}; + dst_info.row = new_entity(world, dst, &dst_info, src_table, &to_add, true); + + if (copy_value) { + flecs_table_move(world, dst, src, src_table, dst_info.data, + dst_info.row, src_table, src_info.data, src_info.row, true); + + flecs_run_set_systems(world, 0, + src_table, src_info.data, NULL, dst_info.row, 1, true); + } + + flecs_defer_flush(world, stage); + + return dst; +} + +const void* ecs_get_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(flecs_stage_from_readonly_world(world)->asynchronous == false, + ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = ecs_eis_get(world, entity); + if (!r) { + return NULL; + } + + ecs_table_t *table = r->table; + if (!table) { + return NULL; + } + + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + ecs_table_record_t *tr = ecs_map_get(idr->table_index, + ecs_table_record_t, table->id); + if (!tr) { + return get_base_component(world, table, id, idr->table_index, NULL, 0); + } + + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + + return get_component_w_index(table, tr->column, row); +} + +const void* ecs_get_ref_w_id( + const ecs_world_t * world, + ecs_ref_t * ref, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!entity || !ref->entity || entity == ref->entity, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!id || !ref->component || id == ref->component, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *record = ref->record; + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + entity |= ref->entity; + + if (!record) { + record = ecs_eis_get(world, entity); + } + + if (!record || !record->table) { + return NULL; + } + + ecs_table_t *table = record->table; + + if (ref->record == record && + ref->table == table && + ref->row == record->row && + ref->alloc_count == table->alloc_count) + { + return ref->ptr; + } + + id |= ref->component; + + int32_t row = record->row; + + ref->entity = entity; + ref->component = id; + ref->table = table; + ref->row = record->row; + ref->alloc_count = table->alloc_count; + + if (table) { + bool is_monitored; + row = flecs_record_to_row(row, &is_monitored); + ref->ptr = get_component(world, table, row, id); + } + + ref->record = record; + + return ref->ptr; +} + +void* ecs_get_mut_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool * is_added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + void *result; + + if (flecs_defer_set( + world, stage, EcsOpMut, entity, id, 0, NULL, &result, is_added)) + { + return result; + } + + ecs_entity_info_t info; + result = get_mutable(world, entity, id, &info, is_added); + + /* Store table so we can quickly check if returned pointer is still valid */ + ecs_table_t *table = info.record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep track of alloc count of table, since even if the entity has not + * moved, other entities could have been added to the table which could + * reallocate arrays. Also store the row, as the entity could have + * reallocated. */ + int32_t alloc_count = table->alloc_count; + int32_t row = info.record->row; + + flecs_defer_flush(world, stage); + + /* Ensure that after flushing, the pointer is still valid. Flushing may + * trigger callbacks, which could do anything with the entity */ + if (table != info.record->table || + alloc_count != info.record->table->alloc_count || + row != info.record->row) + { + if (flecs_get_info(world, entity, &info) && info.table) { + result = get_component(world, info.table, info.row, id); + } else { + /* A trigger has removed the component we just added. This is not + * allowed, an application should always be able to assume that + * get_mut returns a valid pointer. */ + ecs_assert(false, ECS_INVALID_OPERATION, NULL); + } + } + + return result; +} + +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + void *result; + + if (flecs_defer_set( + world, stage, EcsOpMut, entity, id, 0, NULL, &result, NULL)) + { + return result; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_ids_t to_add = { + .array = &id, + .count = 1 + }; + + add_ids_w_info(world, entity, &info, &to_add, + false /* Add component without constructing it */ ); + + void *ptr = get_component(world, info.table, info.row, id); + + flecs_defer_flush(world, stage); + + return ptr; +} + +void ecs_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_modified(world, 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_assert(ecs_has_id(world, entity, id), + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_info_t info = {0}; + if (flecs_get_info(world, entity, &info)) { + ecs_column_t *column = ecs_table_column_for_id(world, info.table, id); + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_run_set_systems(world, id, + info.table, info.data, column, info.row, 1, false); + } + + flecs_table_mark_dirty(info.table, id); + + flecs_defer_flush(world, stage); +} + +static +ecs_entity_t assign_ptr_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void * ptr, + bool is_move, + bool notify) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (!entity) { + entity = ecs_new_id(world); + ecs_entity_t scope = stage->scope; + if (scope) { + ecs_add_pair(world, entity, EcsChildOf, scope); + } + } + + if (flecs_defer_set(world, stage, EcsOpSet, entity, id, + flecs_from_size_t(size), ptr, NULL, NULL)) + { + return entity; + } + + ecs_entity_info_t info; + + void *dst = get_mutable(world, entity, id, &info, NULL); + + /* This can no longer happen since we defer operations */ + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ptr) { + ecs_entity_t real_id = ecs_get_typeid(world, id); + const ecs_type_info_t *cdata = get_c_info(world, real_id); + if (cdata) { + if (is_move) { + ecs_move_t move = cdata->lifecycle.move; + if (move) { + move(world, real_id, &entity, &entity, dst, ptr, size, 1, + cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, ptr, flecs_from_size_t(size)); + } + } else { + ecs_copy_t copy = cdata->lifecycle.copy; + if (copy) { + copy(world, real_id, &entity, &entity, dst, ptr, size, 1, + cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, ptr, flecs_from_size_t(size)); + } + } + } else { + ecs_os_memcpy(dst, ptr, flecs_from_size_t(size)); + } + } else { + memset(dst, 0, size); + } + + flecs_table_mark_dirty(info.table, id); + + if (notify) { + ecs_column_t *column = ecs_table_column_for_id(world, info.table, id); + flecs_run_set_systems(world, id, + info.table, info.data, column, info.row, 1, false); + } + + flecs_defer_flush(world, stage); + + return entity; +} + +ecs_entity_t ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!entity || ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + /* Safe to cast away const: function won't modify if move arg is false */ + return assign_ptr_w_id( + world, entity, id, size, (void*)ptr, false, true); +} + +ecs_entity_t ecs_get_case( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t sw_id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, sw_id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_entity_info_t info; + ecs_table_t *table; + if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + return 0; + } + + sw_id = sw_id | ECS_SWITCH; + + ecs_type_t type = table->type; + int32_t index = ecs_type_index_of(type, 0, sw_id); + if (index == -1) { + return 0; + } + + index -= table->sw_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = info.data->sw_columns[index].data; + return flecs_switch_get(sw, info.row); +} + +void ecs_enable_component_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_enable( + world, stage, entity, id, enable)) + { + return; + } else { + /* Operations invoked by enable/disable should not be deferred */ + stage->defer --; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; + + ecs_table_t *table = info.table; + int32_t index = -1; + if (table) { + index = ecs_type_index_of(table->type, 0, bs_id); + } + + if (index == -1) { + ecs_add_id(world, entity, bs_id); + ecs_enable_component_w_id(world, entity, id, enable); + return; + } + + index -= table->bs_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &info.data->bs_columns[index].data; + ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_bitset_set(bs, info.row, enable); +} + +bool ecs_is_component_enabled_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_info_t info; + ecs_table_t *table; + if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + return false; + } + + ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; + + ecs_type_t type = table->type; + int32_t index = ecs_type_index_of(type, 0, bs_id); + if (index == -1) { + /* If table does not have DISABLED column for component, component is + * always enabled, if the entity has it */ + return ecs_has_id(world, entity, id); + } + + index -= table->bs_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &info.data->bs_columns[index].data; + + return flecs_bitset_get(bs, info.row); +} + +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + if (ECS_HAS_ROLE(id, CASE)) { + ecs_entity_info_t info; + ecs_table_t *table; + if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + return false; + } + + int32_t index = flecs_table_switch_from_case(world, table, id); + ecs_assert(index < table->sw_column_count, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = info.data; + ecs_switch_t *sw = data->sw_columns[index].data; + ecs_entity_t value = flecs_switch_get(sw, info.row); + + return value == (id & ECS_COMPONENT_MASK); + } else { + ecs_table_t *table = ecs_get_table(world, entity); + if (!table) { + return false; + } + + return ecs_type_match( + world, table, table->type, 0, id, EcsIsA, 0, 0, NULL) != -1; + } +} + +bool ecs_has_type( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + return has_type(world, entity, type, true, true); +} + +ecs_entity_t ecs_get_object( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(rel != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + if (!entity) { + return 0; + } + + ecs_table_t *table = ecs_get_table(world, entity); + if (!table) { + return 0; + } + + ecs_id_t wc = ecs_pair(rel, EcsWildcard); + ecs_table_record_t *tr = flecs_get_table_record(world, table, wc); + if (!tr) { + return 0; + } + + if (index >= tr->count) { + return 0; + } + + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + return ecs_pair_object(world, ids[tr->column + index]); +} + +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, EcsName); + + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +const char* ecs_get_symbol( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, EcsSymbol); + + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + entity = ecs_new_id(world); + } + + ecs_set_pair(world, entity, EcsIdentifier, EcsName, {.value = (char*)name}); + + return entity; +} + +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + entity = ecs_new_id(world); + } + + ecs_set_pair(world, entity, EcsIdentifier, EcsSymbol, { + .value = (char*)name + }); + + return entity; +} + +ecs_type_t ecs_type_from_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + if (!id) { + return NULL; + } + + if (!(id & ECS_ROLE_MASK)) { + const EcsType *type = ecs_get(world, id, EcsType); + if (type) { + return type->normalized; + } + } + + ecs_ids_t ids = { + .array = &id, + .count = 1 + }; + + ecs_table_t *table = flecs_table_find_or_create(world, &ids); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +ecs_id_t ecs_type_to_id( + const ecs_world_t *world, + ecs_type_t type) +{ + (void)world; + + if (!type) { + return 0; + } + + /* If array contains n entities, it cannot be reduced to a single entity */ + if (ecs_vector_count(type) != 1) { + ecs_abort(ECS_TYPE_NOT_AN_ENTITY, NULL); + } + + return *(ecs_vector_first(type, ecs_id_t)); +} + +ecs_id_t ecs_make_pair( + ecs_entity_t relation, + ecs_entity_t object) +{ + return ecs_pair(relation, object); +} + +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* 0 is not a valid entity id */ + if (!entity) { + return false; + } + + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return false; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + /* When checking roles and/or pairs, the generation count may have been + * stripped away. Just test if the entity is 0 or not. */ + if (ECS_HAS_ROLE(entity, PAIR)) { + ecs_entity_t lo = ECS_PAIR_OBJECT(entity); + ecs_entity_t hi = ECS_PAIR_RELATION(entity); + return lo != 0 && hi != 0; + } else + if (entity & ECS_ROLE) { + return ecs_entity_t_lo(entity) != 0; + } + + /* An id may not yet exist in the world which does not mean it cannot be + * used as an entity identifier. An example is when a hard-coded entity id + * is used. However, if the entity id does exist in the world, it must be + * alive. */ + return !ecs_exists(world, entity) || ecs_is_alive(world, entity); +} + +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + return ecs_eis_is_alive(world, entity); +} + +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + if (ecs_is_alive(world, entity)) { + return entity; + } + + /* Make sure id does not have generation. This guards against accidentally + * "upcasting" a not alive identifier to a alive one. */ + ecs_assert((uint32_t)entity == entity, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_t current = ecs_eis_get_current(world, entity); + if (!current || !ecs_is_alive(world, current)) { + return 0; + } + + return current; +} + +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + if (ecs_eis_is_alive(world, entity)) { + /* Nothing to be done, already alive */ + return; + } + + /* Ensure id exists. The underlying datastructure will verify that the + * generation count matches the provided one. */ + ecs_eis_ensure(world, entity); +} + +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + return ecs_eis_exists(world, entity); +} + +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_record_t *record = ecs_eis_get(world, entity); + ecs_table_t *table; + if (record && (table = record->table)) { + return table; + } + + return NULL; +} + +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; +} + +ecs_id_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + if (ECS_HAS_ROLE(id, PAIR)) { + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_t rel = ecs_get_alive(world, ECS_PAIR_RELATION(id)); + + /* If relation is marked as a tag, it never has data. Return relation */ + if (ecs_has_id(world, rel, EcsTag)) { + return 0; + } + + const EcsComponent *ptr = ecs_get(world, rel, EcsComponent); + if (ptr && ptr->size != 0) { + return rel; + } else { + ecs_entity_t obj = ecs_get_alive(world, ECS_PAIR_OBJECT(id)); + ptr = ecs_get(world, obj, EcsComponent); + + if (ptr && ptr->size != 0) { + return obj; + } + + /* Neither relation nor object have data */ + return 0; + } + + } else if (id & ECS_ROLE_MASK) { + return 0; + } + + return id; +} + +int32_t ecs_count_type( + const ecs_world_t *world, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!type) { + return 0; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + return ecs_count_filter(world, &(ecs_filter_t){ + .include = type + }); +} + +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return 0; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + /* Get temporary type that just contains entity */ + ECS_VECTOR_STACK(type, ecs_entity_t, &entity, 1); + + return ecs_count_filter(world, &(ecs_filter_t){ + .include = type + }); +} + +int32_t ecs_count_filter( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + int32_t result = 0; + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + if (!filter || flecs_table_match_filter(world, table, filter)) { + result += ecs_table_count(table); + } + } + + return result; +} + +bool ecs_defer_begin( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_none(world, stage); +} + +bool ecs_defer_end( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_flush(world, stage); +} + +static +size_t append_to_str( + char **buffer, + const char *str, + size_t bytes_left, + size_t *required) +{ + char *ptr = NULL; + if (buffer) { + ptr = *buffer; + } + + size_t len = strlen(str); + size_t to_write; + if (bytes_left < len) { + to_write = bytes_left; + bytes_left = 0; + } else { + to_write = len; + bytes_left -= len; + } + + if (to_write && ptr) { + ecs_os_memcpy(ptr, str, to_write); + } + + (*required) += len; + + if (buffer) { + (*buffer) += to_write; + } + + return bytes_left; +} + +const char* ecs_role_str( + ecs_entity_t entity) +{ + if (ECS_HAS_ROLE(entity, PAIR)) { + return "PAIR"; + } else + if (ECS_HAS_ROLE(entity, DISABLED)) { + return "DISABLED"; + } else + if (ECS_HAS_ROLE(entity, XOR)) { + return "XOR"; + } else + if (ECS_HAS_ROLE(entity, OR)) { + return "OR"; + } else + if (ECS_HAS_ROLE(entity, AND)) { + return "AND"; + } else + if (ECS_HAS_ROLE(entity, NOT)) { + return "NOT"; + } else + if (ECS_HAS_ROLE(entity, SWITCH)) { + return "SWITCH"; + } else + if (ECS_HAS_ROLE(entity, CASE)) { + return "CASE"; + } else + if (ECS_HAS_ROLE(entity, OWNED)) { + return "OWNED"; + } else { + return "UNKNOWN"; + } +} + +size_t ecs_id_str( + const ecs_world_t *world, + ecs_id_t id, + char *buffer, + size_t buffer_len) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + char *ptr = buffer; + char **pptr = NULL; + if (ptr) { + pptr = &ptr; + } + + size_t bytes_left = buffer_len - 1, required = 0; + if (id & ECS_ROLE_MASK && !ECS_HAS_ROLE(id, PAIR)) { + const char *role = ecs_role_str(id); + bytes_left = append_to_str(pptr, role, bytes_left, &required); + bytes_left = append_to_str(pptr, "|", bytes_left, &required); + } + + ecs_entity_t e = id & ECS_COMPONENT_MASK; + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t lo = ECS_PAIR_OBJECT(id); + ecs_entity_t hi = ECS_PAIR_RELATION(id); + + if (lo) lo = ecs_get_alive(world, lo); + if (hi) hi = ecs_get_alive(world, hi); + + if (hi) { + char *hi_path = ecs_get_fullpath(world, hi); + bytes_left = append_to_str(pptr, "(", bytes_left, &required); + bytes_left = append_to_str(pptr, hi_path, bytes_left, &required); + ecs_os_free(hi_path); + bytes_left = append_to_str(pptr, ",", bytes_left, &required); + } + + char *lo_path = ecs_get_fullpath(world, lo); + bytes_left = append_to_str(pptr, lo_path, bytes_left, &required); + ecs_os_free(lo_path); + + if (hi) { + append_to_str(pptr, ")", bytes_left, &required); + } + } else { + char *path = ecs_get_fullpath(world, e); + append_to_str(pptr, path, bytes_left, &required); + ecs_os_free(path); + } + + if (ptr) { + ptr[0] = '\0'; + } + + return required; +} + +static +void flush_bulk_new( + ecs_world_t * world, + ecs_op_t * op) +{ + ecs_entity_t *ids = op->is._n.entities; + void **bulk_data = op->is._n.bulk_data; + if (bulk_data) { + ecs_entity_t *components = op->components.array; + int c, c_count = op->components.count; + for (c = 0; c < c_count; c ++) { + ecs_entity_t component = components[c]; + const EcsComponent *cptr = flecs_component_from_id(world, component); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + size_t size = flecs_to_size_t(cptr->size); + void *ptr, *data = bulk_data[c]; + int i, count = op->is._n.count; + for (i = 0, ptr = data; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + assign_ptr_w_id(world, ids[i], component, size, ptr, + true, true); + } + ecs_os_free(data); + } + ecs_os_free(bulk_data); + } else { + int i, count = op->is._n.count; + for (i = 0; i < count; i ++) { + add_ids(world, ids[i], &op->components); + } + } + + if (op->components.count > 1) { + ecs_os_free(op->components.array); + } + + ecs_os_free(ids); +} + +static +void free_value( + ecs_world_t *world, + ecs_entity_t *entities, + ecs_id_t id, + void *value, + int32_t count) +{ + ecs_entity_t real_id = ecs_get_typeid(world, id); + const ecs_type_info_t *info = flecs_get_c_info(world, real_id); + ecs_xtor_t dtor; + + if (info && (dtor = info->lifecycle.dtor)) { + ecs_size_t size = info->size; + void *ptr; + int i; + for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + dtor(world, id, &entities[i], ptr, flecs_to_size_t(size), 1, + info->lifecycle.ctx); + } + } +} + +static +void discard_op( + ecs_world_t *world, + ecs_op_t * op) +{ + if (op->kind == EcsOpBulkNew) { + void **bulk_data = op->is._n.bulk_data; + if (bulk_data) { + ecs_entity_t *entities = op->is._n.entities; + ecs_entity_t *components = op->components.array; + int c, c_count = op->components.count; + for (c = 0; c < c_count; c ++) { + free_value(world, entities, components[c], bulk_data[c], + op->is._n.count); + ecs_os_free(bulk_data[c]); + } + } + } else { + void *value = op->is._1.value; + if (value) { + free_value(world, &op->is._1.entity, op->component, op->is._1.value, 1); + ecs_os_free(value); + } + } + + ecs_entity_t *components = op->components.array; + if (components) { + ecs_os_free(components); + } +} + +static +bool is_entity_valid( + ecs_world_t *world, + ecs_entity_t e) +{ + if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { + return false; + } + return true; +} + +static +bool remove_invalid( + ecs_world_t * world, + ecs_ids_t * ids) +{ + ecs_entity_t *array = ids->array; + int32_t i, offset = 0, count = ids->count; + + for (i = 0; i < count; i ++) { + ecs_id_t id = array[i]; + bool is_remove = false; + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t rel = ecs_pair_relation(world, id); + if (!rel || !is_entity_valid(world, rel)) { + /* After relation is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + is_remove = true; + } else { + ecs_entity_t obj = ecs_pair_object(world, id); + if (!obj || !is_entity_valid(world, obj)) { + /* Check the relation's policy for deleted objects */ + ecs_id_record_t *idr = flecs_get_id_record(world, rel); + if (!idr || (idr->on_delete_object == EcsRemove)) { + is_remove = true; + } else { + if (idr->on_delete_object == EcsDelete) { + /* Entity should be deleted, don't bother checking + * other ids */ + return false; + } else if (idr->on_delete_object == EcsThrow) { + /* If policy is throw this object should not have + * been deleted */ + throw_invalid_delete(world, id); + } + } + } + } + + } else { + id &= ECS_COMPONENT_MASK; + + if (!is_entity_valid(world, id)) { + /* After relation is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + is_remove = true; + } + } + + if (is_remove) { + offset ++; + count --; + } + + ids->array[i] = ids->array[i + offset]; + } + + ids->count = count; + + return true; +} + +/* Leave safe section. Run all deferred commands. */ +bool flecs_defer_flush( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!--stage->defer) { + /* Set to NULL. Processing deferred commands can cause additional + * commands to get enqueued (as result of reactive systems). Make sure + * that the original array is not reallocated, as this would complicate + * processing the queue. */ + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + + if (defer_queue) { + ecs_op_t *ops = ecs_vector_first(defer_queue, ecs_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + + for (i = 0; i < count; i ++) { + ecs_op_t *op = &ops[i]; + ecs_entity_t e = op->is._1.entity; + if (op->kind == EcsOpBulkNew) { + e = 0; + } + + /* If entity is no longer alive, this could be because the queue + * contained both a delete and a subsequent add/remove/set which + * should be ignored. */ + if (e && !ecs_is_alive(world, e) && ecs_eis_exists(world, e)) { + ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone, + ECS_INTERNAL_ERROR, NULL); + world->discard_count ++; + discard_op(world, op); + continue; + } + + if (op->components.count == 1) { + op->components.array = &op->component; + } + + switch(op->kind) { + case EcsOpNew: + case EcsOpAdd: + if (remove_invalid(world, &op->components)) { + world->add_count ++; + add_ids(world, e, &op->components); + } else { + ecs_delete(world, e); + } + break; + case EcsOpRemove: + remove_ids(world, e, &op->components); + break; + case EcsOpClone: + ecs_clone(world, e, op->component, op->is._1.clone_value); + break; + case EcsOpSet: + assign_ptr_w_id(world, e, + op->component, flecs_to_size_t(op->is._1.size), + op->is._1.value, true, true); + break; + case EcsOpMut: + assign_ptr_w_id(world, e, + op->component, flecs_to_size_t(op->is._1.size), + op->is._1.value, true, false); + break; + case EcsOpModified: + ecs_modified_id(world, e, op->component); + break; + case EcsOpDelete: { + ecs_delete(world, e); + break; + } + case EcsOpEnable: + ecs_enable_component_w_id( + world, e, op->component, true); + break; + case EcsOpDisable: + ecs_enable_component_w_id( + world, e, op->component, false); + break; + case EcsOpClear: + ecs_clear(world, e); + break; + case EcsOpBulkNew: + flush_bulk_new(world, op); + + /* Continue since flush_bulk_new is repsonsible for cleaning + * up resources. */ + continue; + } + + if (op->components.count > 1) { + ecs_os_free(op->components.array); + } + + if (op->is._1.value) { + ecs_os_free(op->is._1.value); + } + } + + if (stage->defer_queue) { + ecs_vector_free(stage->defer_queue); + } + + /* Restore defer queue */ + ecs_vector_clear(defer_queue); + stage->defer_queue = defer_queue; + } + + return true; + } + + return false; +} + +/* Delete operations from queue without executing them. */ +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!--stage->defer) { + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + + if (defer_queue) { + ecs_op_t *ops = ecs_vector_first(defer_queue, ecs_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + for (i = 0; i < count; i ++) { + discard_op(world, &ops[i]); + } + + if (stage->defer_queue) { + ecs_vector_free(stage->defer_queue); + } + + /* Restore defer queue */ + ecs_vector_clear(defer_queue); + stage->defer_queue = defer_queue; + } + + return true; + } + + return false; +} + +static +ecs_op_t* new_defer_op(ecs_stage_t *stage) { + ecs_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_op_t); + ecs_os_memset(result, 0, ECS_SIZEOF(ecs_op_t)); + return result; +} + +static +void new_defer_component_ids( + ecs_op_t *op, + const ecs_ids_t *components) +{ + ecs_assert(components != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t components_count = components->count; + if (components_count == 1) { + ecs_entity_t component = components->array[0]; + op->component = component; + op->components = (ecs_ids_t) { + .array = NULL, + .count = 1 + }; + } else if (components_count) { + ecs_size_t array_size = components_count * ECS_SIZEOF(ecs_entity_t); + op->components.array = ecs_os_malloc(array_size); + ecs_os_memcpy(op->components.array, components->array, array_size); + op->components.count = components_count; + } else { + op->component = 0; + op->components = (ecs_ids_t){ 0 }; + } +} + +static +bool defer_add_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_ids_t *components) +{ + if (stage->defer) { + if (components) { + if (!components->count) { + return true; + } + } + + ecs_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->is._1.entity = entity; + + new_defer_component_ids(op, components); + + if (op_kind == EcsOpNew) { + world->new_count ++; + } else if (op_kind == EcsOpAdd) { + world->add_count ++; + } else if (op_kind == EcsOpRemove) { + world->remove_count ++; + } + + return true; + } else { + stage->defer ++; + } + + return false; +} + +static +void merge_stages( + ecs_world_t *world, + bool force_merge) +{ + bool is_stage = world->magic == ECS_STAGE_MAGIC; + ecs_stage_t *stage = flecs_stage_from_world(&world); + + bool measure_frame_time = world->measure_frame_time; + + ecs_time_t t_start; + if (measure_frame_time) { + ecs_os_get_time(&t_start); + } + + if (is_stage) { + /* Check for consistency if force_merge is enabled. In practice this + * function will never get called with force_merge disabled for just + * a single stage. */ + if (force_merge || stage->auto_merge) { + ecs_defer_end((ecs_world_t*)stage); + } + } else { + /* Merge stages. Only merge if the stage has auto_merging turned on, or + * if this is a forced merge (like when ecs_merge is called) */ + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(s->magic == ECS_STAGE_MAGIC, ECS_INTERNAL_ERROR, NULL); + if (force_merge || s->auto_merge) { + ecs_defer_end((ecs_world_t*)s); + } + } + } + + flecs_eval_component_monitors(world); + + if (measure_frame_time) { + world->stats.merge_time_total += + (FLECS_FLOAT)ecs_time_measure(&t_start); + } + + world->stats.merge_count_total ++; + + /* If stage is asynchronous, deferring is always enabled */ + if (stage->asynchronous) { + ecs_defer_begin((ecs_world_t*)stage); + } +} + +static +void do_auto_merge( + ecs_world_t *world) +{ + merge_stages(world, false); +} + +static +void do_manual_merge( + ecs_world_t *world) +{ + merge_stages(world, true); +} + +bool flecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + return (++ stage->defer) == 1; +} + +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpModified; + op->component = component; + op->is._1.entity = entity; + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool flecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpClone; + op->component = src; + op->is._1.entity = entity; + op->is._1.clone_value = clone_value; + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool flecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpDelete; + op->is._1.entity = entity; + world->delete_count ++; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool flecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpClear; + op->is._1.entity = entity; + world->clear_count ++; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool flecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = enable ? EcsOpEnable : EcsOpDisable; + op->is._1.entity = entity; + op->component = component; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + const ecs_ids_t *components_ids, + void **component_data, + const ecs_entity_t **ids_out) +{ + if (stage->defer) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); + void **defer_data = NULL; + + world->bulk_new_count ++; + + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); + } + + /* Create private copy for component data */ + if (component_data) { + int c, c_count = components_ids->count; + ecs_entity_t *components = components_ids->array; + defer_data = ecs_os_malloc(ECS_SIZEOF(void*) * c_count); + for (c = 0; c < c_count; c ++) { + ecs_entity_t comp = components[c]; + const EcsComponent *cptr = flecs_component_from_id(world, comp); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t size = cptr->size; + void *data = ecs_os_malloc(size * count); + defer_data[c] = data; + + const ecs_type_info_t *cinfo = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, comp); + if (real_id) { + cinfo = flecs_get_c_info(world, real_id); + } + ecs_xtor_t ctor; + if (cinfo && (ctor = cinfo->lifecycle.ctor)) { + void *ctx = cinfo->lifecycle.ctx; + ctor(world, comp, ids, data, flecs_to_size_t(size), count, ctx); + ecs_move_t move; + if ((move = cinfo->lifecycle.move)) { + move(world, comp, ids, ids, data, component_data[c], + flecs_to_size_t(size), count, ctx); + } else { + ecs_os_memcpy(data, component_data[c], size * count); + } + } else { + ecs_os_memcpy(data, component_data[c], size * count); + } + } + } + + /* Store data in op */ + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpBulkNew; + op->is._n.entities = ids; + op->is._n.bulk_data = defer_data; + op->is._n.count = count; + new_defer_component_ids(op, components_ids); + *ids_out = ids; + + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components) +{ + return defer_add_remove(world, stage, EcsOpNew, entity, components); +} + +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components) +{ + return defer_add_remove(world, stage, EcsOpAdd, entity, components); +} + +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components) +{ + return defer_add_remove(world, stage, EcsOpRemove, entity, components); +} + +bool flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added) +{ + if (stage->defer) { + world->set_count ++; + if (!size) { + const EcsComponent *cptr = flecs_component_from_id(world, component); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + size = cptr->size; + } + + ecs_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->component = component; + op->is._1.entity = entity; + op->is._1.size = size; + op->is._1.value = ecs_os_malloc(size); + + if (!value) { + value = ecs_get_id(world, entity, component); + if (is_added) { + *is_added = value == NULL; + } + } + + const ecs_type_info_t *c_info = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + c_info = flecs_get_c_info(world, real_id); + } + + if (value) { + ecs_copy_ctor_t copy; + if (c_info && (copy = c_info->lifecycle.copy_ctor)) { + copy(world, component, &c_info->lifecycle, &entity, &entity, + op->is._1.value, value, flecs_to_size_t(size), 1, + c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(op->is._1.value, value, size); + } + } else { + ecs_xtor_t ctor; + if (c_info && (ctor = c_info->lifecycle.ctor)) { + ctor(world, component, &entity, op->is._1.value, + flecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } + } + + if (value_out) { + *value_out = op->is._1.value; + } + + return true; + } else { + stage->defer ++; + } + + return false; +} + +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) +{ + /* Execute post frame actions */ + ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { + action->action(world, action->ctx); + }); + + ecs_vector_free(stage->post_frame_actions); + stage->post_frame_actions = NULL; +} + +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + memset(stage, 0, sizeof(ecs_stage_t)); + + stage->magic = ECS_STAGE_MAGIC; + stage->world = world; + stage->thread_ctx = world; + stage->auto_merge = true; + stage->asynchronous = false; +} + +void flecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(stage->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vector_count(stage->defer_queue) == 0, + ECS_INVALID_PARAMETER, NULL); + + /* Set magic to 0 so that accessing the stage after deinitializing it will + * throw an assert. */ + stage->magic = 0; + + ecs_vector_free(stage->defer_queue); +} + +void ecs_set_stages( + ecs_world_t *world, + int32_t stage_count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stages; + int32_t i, count = ecs_vector_count(world->worker_stages); + + if (count && count != stage_count) { + stages = ecs_vector_first(world->worker_stages, ecs_stage_t); + + for (i = 0; i < count; i ++) { + /* If stage contains a thread handle, ecs_set_threads was used to + * create the stages. ecs_set_threads and ecs_set_stages should not + * be mixed. */ + ecs_assert(stages[i].magic == ECS_STAGE_MAGIC, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); + flecs_stage_deinit(world, &stages[i]); + } + + ecs_vector_free(world->worker_stages); + } + + if (stage_count) { + world->worker_stages = ecs_vector_new(ecs_stage_t, stage_count); + + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = ecs_vector_add( + &world->worker_stages, ecs_stage_t); + flecs_stage_init(world, stage); + stage->id = 1 + i; /* 0 is reserved for main/temp stage */ + + /* Set thread_ctx to stage, as this stage might be used in a + * multithreaded context */ + stage->thread_ctx = (ecs_world_t*)stage; + } + } else { + /* Set to NULL to prevent double frees */ + world->worker_stages = NULL; + } + + /* Regardless of whether the stage was just initialized or not, when the + * ecs_set_stages function is called, all stages inherit the auto_merge + * property from the world */ + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = world->stage.auto_merge; + } +} + +int32_t ecs_get_stage_count( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return ecs_vector_count(world->worker_stages); +} + +int32_t ecs_get_stage_id( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (world->magic == ECS_STAGE_MAGIC) { + ecs_stage_t *stage = (ecs_stage_t*)world; + + /* Index 0 is reserved for main stage */ + return stage->id - 1; + } else if (world->magic == ECS_WORLD_MAGIC) { + return 0; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vector_count(world->worker_stages) > stage_id, + ECS_INVALID_PARAMETER, NULL); + + return (ecs_world_t*)ecs_vector_get( + world->worker_stages, ecs_stage_t, stage_id); +} + +bool ecs_staging_begin( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_defer_begin(ecs_get_stage(world, i)); + } + + bool is_readonly = world->is_readonly; + + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + world->is_readonly = true; + + return is_readonly; +} + +void ecs_staging_end( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->is_readonly == true, ECS_INVALID_OPERATION, NULL); + + /* After this it is safe again to mutate the world directly */ + world->is_readonly = false; + + do_auto_merge(world); +} + +void ecs_merge( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + do_manual_merge(world); +} + +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge) +{ + /* If a world is provided, set auto_merge globally for the world. This + * doesn't actually do anything (the main stage never merges) but it serves + * as the default for when stages are created. */ + if (world->magic == ECS_WORLD_MAGIC) { + world->stage.auto_merge = auto_merge; + + /* Propagate change to all stages */ + int i, stage_count = ecs_get_stage_count(world); + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = auto_merge; + } + + /* If a stage is provided, override the auto_merge value for the individual + * stage. This allows an application to control per-stage which stage should + * be automatically merged and which one shouldn't */ + } else { + /* Magic needs to be either a world or a stage */ + ecs_assert(world->magic == ECS_STAGE_MAGIC, + ECS_INVALID_FROM_WORKER, NULL); + + ecs_stage_t *stage = (ecs_stage_t*)world; + stage->auto_merge = auto_merge; + } +} + +bool ecs_stage_is_readonly( + const ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); + + if (stage->magic == ECS_STAGE_MAGIC) { + if (((ecs_stage_t*)stage)->asynchronous) { + return false; + } + } + + if (world->is_readonly) { + if (stage->magic == ECS_WORLD_MAGIC) { + return true; + } + } else { + if (stage->magic == ECS_STAGE_MAGIC) { + return true; + } + } + + return false; +} + +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_stage_init(world, stage); + + stage->id = -1; + stage->auto_merge = false; + stage->asynchronous = true; + + ecs_defer_begin((ecs_world_t*)stage); + + return (ecs_world_t*)stage; +} + +void ecs_async_stage_free( + ecs_world_t *world) +{ + ecs_assert(world->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_assert(stage->asynchronous == true, ECS_INVALID_PARAMETER, NULL); + flecs_stage_deinit(stage->world, stage); + ecs_os_free(stage); +} + +bool ecs_stage_is_async( + ecs_world_t *stage) +{ + if (!stage) { + return false; + } + + if (stage->magic != ECS_STAGE_MAGIC) { + return false; + } + + return ((ecs_stage_t*)stage)->asynchronous; +} + +bool ecs_is_deferred( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->defer != 0; +} + +/** Resize the vector buffer */ +static +ecs_vector_t* resize( + ecs_vector_t *vector, + int16_t offset, + int32_t size) +{ + ecs_vector_t *result = ecs_os_realloc(vector, offset + size); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); + return result; +} + +/* -- Public functions -- */ + +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->count = 0; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; +} + +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); + + result->count = elem_count; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; +} + +void ecs_vector_free( + ecs_vector_t *vector) +{ + ecs_os_free(vector); +} + +void ecs_vector_clear( + ecs_vector_t *vector) +{ + if (vector) { + vector->count = 0; + } +} + +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + void *array = ECS_OFFSET(vector, offset); + ecs_os_memset(array, 0, elem_size * vector->count); +} + +void ecs_vector_assert_size( + ecs_vector_t *vector, + ecs_size_t elem_size) +{ + (void)elem_size; + + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + } +} + +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); + + if (elem_count == 1) { + return _ecs_vector_add(array_inout, elem_size, offset); + } + + ecs_vector_t *vector = *array_inout; + if (!vector) { + vector = _ecs_vector_new(elem_size, offset, 1); + *array_inout = vector; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t max_count = vector->size; + int32_t old_count = vector->count; + int32_t new_count = old_count + elem_count; + + if ((new_count - 1) >= max_count) { + if (!max_count) { + max_count = elem_count; + } else { + while (max_count < new_count) { + max_count *= 2; + } + } + + vector = resize(vector, offset, max_count * elem_size); + vector->size = max_count; + *array_inout = vector; + } + + vector->count = new_count; + + return ECS_OFFSET(vector, offset + elem_size * old_count); +} + +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_vector_t *vector = *array_inout; + int32_t count, size; + + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + count = vector->count; + size = vector->size; + + if (count >= size) { + size *= 2; + if (!size) { + size = 2; + } + vector = resize(vector, offset, size * elem_size); + *array_inout = vector; + vector->size = size; + } + + vector->count = count + 1; + return ECS_OFFSET(vector, offset + elem_size * count); + } + + vector = _ecs_vector_new(elem_size, offset, 2); + *array_inout = vector; + vector->count = 1; + vector->size = 2; + return ECS_OFFSET(vector, offset); +} + +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + void *dst_elem = _ecs_vector_add(dst, elem_size, offset); + void *src_elem = _ecs_vector_get(src, elem_size, offset, index); + + ecs_os_memcpy(dst_elem, src_elem, elem_size); + return _ecs_vector_remove(src, elem_size, offset, index); +} + +void ecs_vector_remove_last( + ecs_vector_t *vector) +{ + if (vector && vector->count) vector->count --; +} + +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value) +{ + if (!vector) { + return false; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + if (!count) { + return false; + } + + void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); + + if (value) { + ecs_os_memcpy(value, elem, elem_size); + } + + ecs_vector_remove_last(vector); + + return true; +} + +int32_t _ecs_vector_remove( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + void *elem = ECS_OFFSET(buffer, index * elem_size); + + ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); + + count --; + if (index != count) { + void *last_elem = ECS_OFFSET(buffer, elem_size * count); + ecs_os_memcpy(elem, last_elem, elem_size); + } + + vector->count = count; + + return count; +} + +void _ecs_vector_reclaim( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_vector_t *vector = *array_inout; + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t size = vector->size; + int32_t count = vector->count; + + if (count < size) { + size = count; + vector = resize(vector, offset, size * elem_size); + vector->size = size; + *array_inout = vector; + } +} + +int32_t ecs_vector_count( + const ecs_vector_t *vector) +{ + if (!vector) { + return 0; + } + return vector->count; +} + +int32_t ecs_vector_size( + const ecs_vector_t *vector) +{ + if (!vector) { + return 0; + } + return vector->size; +} + +int32_t _ecs_vector_set_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_vector_t *vector = *array_inout; + + if (!vector) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + return elem_count; + } else { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t result = vector->size; + + if (elem_count < vector->count) { + elem_count = vector->count; + } + + if (result < elem_count) { + elem_count = flecs_next_pow_of_2(elem_count); + vector = resize(vector, offset, elem_count * elem_size); + vector->size = elem_count; + *array_inout = vector; + result = elem_count; + } + + return result; + } +} + +int32_t _ecs_vector_grow( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + int32_t current = ecs_vector_count(*array_inout); + return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); +} + +int32_t _ecs_vector_set_count( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + if (!*array_inout) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + } + + ecs_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + (*array_inout)->count = elem_count; + ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); + return size; +} + +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + (void)elem_size; + + ecs_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + if (vector && vector->size) { + return ECS_OFFSET(vector, offset); + } else { + return NULL; + } +} + +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + if (!vector) { + return NULL; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + + if (index >= count) { + return NULL; + } + + return ECS_OFFSET(vector, offset + elem_size * index); +} + +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + int32_t count = vector->count; + if (!count) { + return NULL; + } else { + return ECS_OFFSET(vector, offset + elem_size * (count - 1)); + } + } else { + return NULL; + } +} + +int32_t _ecs_vector_set_min_size( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + if (!*vector_inout || (*vector_inout)->size < elem_count) { + return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); + } else { + return (*vector_inout)->size; + } +} + +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); + + ecs_vector_t *v = *vector_inout; + if (v && v->count < elem_count) { + v->count = elem_count; + } + + return v->count; +} + +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action) +{ + if (!vector) { + return; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + + if (count > 1) { + qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); + } +} + +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used) +{ + if (!vector) { + return; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + if (allocd) { + *allocd += vector->size * elem_size + offset; + } + if (used) { + *used += vector->count * elem_size; + } +} + +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset) +{ + if (!src) { + return NULL; + } + + ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); + ecs_os_memcpy(dst, src, offset + elem_size * src->count); + return dst; +} + +/** The number of elements in a single chunk */ +#define CHUNK_COUNT (4096) + +/** Compute the chunk index from an id by stripping the first 12 bits */ +#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) + +/** This computes the offset of an index inside a chunk */ +#define OFFSET(index) ((int32_t)index & 0xFFF) + +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + +typedef struct chunk_t { + int32_t *sparse; /* Sparse array with indices to dense array */ + void *data; /* Store data in sparse array to reduce + * indirection and provide stable pointers. */ +} chunk_t; + +struct ecs_sparse_t { + ecs_vector_t *dense; /* Dense array with indices to sparse array. The + * dense array stores both alive and not alive + * sparse indices. The 'count' member keeps + * track of which indices are alive. */ + + ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id_local; /* Local max index (if no global is set) */ + uint64_t *max_id; /* Maximum issued sparse index */ +}; + +static +chunk_t* chunk_new( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + int32_t count = ecs_vector_count(sparse->chunks); + chunk_t *chunks; + + if (count <= chunk_index) { + ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1); + chunks = ecs_vector_first(sparse->chunks, chunk_t); + ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t)); + } else { + chunks = ecs_vector_first(sparse->chunks, chunk_t); + } + + ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); + + chunk_t *result = &chunks[chunk_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); + + /* Initialize sparse array with zero's, as zero is used to indicate that the + * sparse element has not been paired with a dense element. Use zero + * as this means we can take advantage of calloc having a possibly better + * performance than malloc + memset. */ + result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT); + + /* Initialize the data array with zero's to guarantee that data is + * always initialized. When an entry is removed, data is reset back to + * zero. Initialize now, as this can take advantage of calloc. */ + result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT); + + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +static +void chunk_free( + chunk_t *chunk) +{ + ecs_os_free(chunk->sparse); + ecs_os_free(chunk->data); +} + +static +chunk_t* get_chunk( + const ecs_sparse_t *sparse, + int32_t chunk_index) +{ + /* If chunk_index is below zero, application used an invalid entity id */ + ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL); + chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index); + if (result && !result->sparse) { + return NULL; + } + + return result; +} + +static +chunk_t* get_or_create_chunk( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + chunk_t *chunk = get_chunk(sparse, chunk_index); + if (chunk) { + return chunk; + } + + return chunk_new(sparse, chunk_index); +} + +static +void grow_dense( + ecs_sparse_t *sparse) +{ + ecs_vector_add(&sparse->dense, uint64_t); +} + +static +uint64_t strip_generation( + uint64_t *index_out) +{ + uint64_t index = *index_out; + uint64_t gen = index & ECS_GENERATION_MASK; + /* Make sure there's no junk in the id */ + ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), + ECS_INVALID_PARAMETER, NULL); + *index_out -= gen; + return gen; +} + +static +void assign_index( + chunk_t * chunk, + uint64_t * dense_array, + uint64_t index, + int32_t dense) +{ + /* Initialize sparse-dense pair. This assigns the dense index to the sparse + * array, and the sparse index to the dense array .*/ + chunk->sparse[OFFSET(index)] = dense; + dense_array[dense] = index; +} + +static +uint64_t inc_gen( + uint64_t index) +{ + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); +} + +static +uint64_t inc_id( + ecs_sparse_t *sparse) +{ + /* Generate a new id. The last issued id could be stored in an external + * variable, such as is the case with the last issued entity id, which is + * stored on the world. */ + return ++ (sparse->max_id[0]); +} + +static +uint64_t get_id( + const ecs_sparse_t *sparse) +{ + return sparse->max_id[0]; +} + +static +void set_id( + ecs_sparse_t *sparse, + uint64_t value) +{ + /* Sometimes the max id needs to be assigned directly, which typically + * happens when the API calls get_or_create for an id that hasn't been + * issued before. */ + sparse->max_id[0] = value; +} + +/* Pair dense id with new sparse id */ +static +uint64_t create_id( + ecs_sparse_t *sparse, + int32_t dense) +{ + uint64_t index = inc_id(sparse); + grow_dense(sparse); + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + assign_index(chunk, dense_array, index, dense); + + return index; +} + +/* Create new id */ +static +uint64_t new_index( + ecs_sparse_t *sparse) +{ + ecs_vector_t *dense = sparse->dense; + int32_t dense_count = ecs_vector_count(dense); + int32_t count = sparse->count ++; + + 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_vector_first(dense, uint64_t); + return dense_array[count]; + } else { + return create_id(sparse, count); + } +} + +/* Try obtaining a value from the sparse set, don't care about whether the + * provided index matches the current generation count. */ +static +void* try_sparse_any( + const ecs_sparse_t *sparse, + uint64_t index) +{ + strip_generation(&index); + + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); +} + +/* Try obtaining a value from the sparse set, make sure it's alive. */ +static +void* try_sparse( + const ecs_sparse_t *sparse, + uint64_t index) +{ + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + uint64_t gen = strip_generation(&index); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + + if (cur_gen != gen) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); +} + +/* Get value from sparse set when it is guaranteed that the value exists. This + * function is used when values are obtained using a dense index */ +static +void* get_sparse( + const ecs_sparse_t *sparse, + int32_t dense, + uint64_t index) +{ + strip_generation(&index); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; + + return DATA(chunk->data, sparse->size, offset); +} + +/* Swap dense elements. A swap occurs when an element is removed, or when a + * removed element is recycled. */ +static +void swap_dense( + ecs_sparse_t * sparse, + chunk_t * chunk_a, + int32_t a, + int32_t b) +{ + ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t index_a = dense_array[a]; + uint64_t index_b = dense_array[b]; + + chunk_t *chunk_b = get_or_create_chunk(sparse, CHUNK(index_b)); + assign_index(chunk_a, dense_array, index_a, b); + assign_index(chunk_b, dense_array, index_b, a); +} + +ecs_sparse_t* _flecs_sparse_new( + ecs_size_t size) +{ + ecs_sparse_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_sparse_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + result->size = size; + result->max_id_local = UINT64_MAX; + result->max_id = &result->max_id_local; + + /* Consume first value in dense array as 0 is used in the sparse array to + * indicate that a sparse element hasn't been paired yet. */ + uint64_t *first = ecs_vector_add(&result->dense, uint64_t); + *first = 0; + + result->count = 1; + + return result; +} + +void flecs_sparse_set_id_source( + ecs_sparse_t * sparse, + uint64_t * id_source) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + sparse->max_id = id_source; +} + +void flecs_sparse_clear( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_vector_each(sparse->chunks, chunk_t, chunk, { + chunk_free(chunk); + }); + + ecs_vector_free(sparse->chunks); + ecs_vector_set_count(&sparse->dense, uint64_t, 1); + + sparse->chunks = NULL; + sparse->count = 1; + sparse->max_id_local = 0; +} + +void flecs_sparse_free( + ecs_sparse_t *sparse) +{ + if (sparse) { + flecs_sparse_clear(sparse); + ecs_vector_free(sparse->dense); + ecs_os_free(sparse); + } +} + +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return new_index(sparse); +} + +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t new_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t dense_count = ecs_vector_count(sparse->dense); + int32_t count = sparse->count; + int32_t remaining = dense_count - count; + int32_t i, to_create = new_count - remaining; + + if (to_create > 0) { + flecs_sparse_set_size(sparse, dense_count + to_create); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + + for (i = 0; i < to_create; i ++) { + uint64_t index = create_id(sparse, count + i); + dense_array[dense_count + i] = index; + } + } + + sparse->count += new_count; + + return ecs_vector_get(sparse->dense, uint64_t, count); +} + +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 = new_index(sparse); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, size, OFFSET(index)); +} + +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; +} + +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_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint64_t gen = strip_generation(&index); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + /* Check if element is alive. If element is not alive, update indices so + * that the first unused dense element points to the sparse element. */ + int32_t count = sparse->count; + if (dense == count) { + /* If dense is the next unused element in the array, simply increase + * the count to make it part of the alive set. */ + sparse->count ++; + } else if (dense > count) { + /* If dense is not alive, swap it with the first unused element. */ + swap_dense(sparse, chunk, dense, count); + + /* First unused element is now last used element */ + sparse->count ++; + } 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. */ + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); + (void)dense_vector; + (void)dense_array; + } else { + /* Element is not paired yet. Must add a new element to dense array */ + grow_dense(sparse); + + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + int32_t dense_count = ecs_vector_count(dense_vector) - 1; + int32_t count = sparse->count ++; + + /* If index is larger than max id, update max id */ + if (index >= get_id(sparse)) { + set_id(sparse, index + 1); + } + + if (count < dense_count) { + /* If there are unused elements in the list, move the first unused + * element to the end of the list */ + uint64_t unused = dense_array[count]; + chunk_t *unused_chunk = get_or_create_chunk(sparse, CHUNK(unused)); + assign_index(unused_chunk, dense_array, unused, dense_count); + } + + assign_index(chunk, dense_array, index, count); + dense_array[count] |= gen; + } + + return DATA(chunk->data, sparse->size, offset); +} + +void* _flecs_sparse_set( + ecs_sparse_t * sparse, + ecs_size_t elem_size, + uint64_t index, + void * value) +{ + void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); + ecs_os_memcpy(ptr, value, elem_size); + return ptr; +} + +void* _flecs_sparse_remove_get( + 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; + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + uint64_t gen = strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (gen != cur_gen) { + /* Generation doesn't match which means that the provided entity is + * already not alive. */ + return NULL; + } + + /* Increase generation */ + dense_array[dense] = index | inc_gen(cur_gen); + + int32_t count = sparse->count; + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + swap_dense(sparse, chunk, dense, count - 1); + sparse->count --; + } else { + /* Element is not alive, nothing to be done */ + return NULL; + } + + /* Reset memory to zero on remove */ + return DATA(chunk->data, sparse->size, offset); + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return NULL; + } +} + +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t index) +{ + void *ptr = _flecs_sparse_remove_get(sparse, 0, index); + if (ptr) { + ecs_os_memset(ptr, 0, sparse->size); + } +} + +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + + uint64_t index_w_gen = index; + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + /* Increase generation */ + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + dense_array[dense] = index_w_gen; + } else { + /* Element is not paired and thus not alive, nothing to be done */ + } +} + +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return false; + } + + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + return dense != 0; +} + +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_vector_first(sparse->dense, uint64_t); + return get_sparse(sparse, dense_index, dense_array[dense_index]); +} + +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) +{ + return try_sparse(sparse, index) != NULL; +} + +uint64_t flecs_sparse_get_alive( + const ecs_sparse_t *sparse, + uint64_t index) +{ + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return 0; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + + /* If dense is 0 (tombstone) this will return 0 */ + return dense_array[dense]; +} + +void* _flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse(sparse, index); +} + +void* _flecs_sparse_get_any( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse_any(sparse, index); +} + +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) +{ + if (!sparse) { + return 0; + } + + return sparse->count - 1; +} + +int32_t flecs_sparse_size( + const ecs_sparse_t *sparse) +{ + if (!sparse) { + return 0; + } + + return ecs_vector_count(sparse->dense) - 1; +} + +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return &(ecs_vector_first(sparse->dense, uint64_t)[1]); +} + +void flecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); +} + +static +void sparse_copy( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + flecs_sparse_set_size(dst, flecs_sparse_size(src)); + const uint64_t *indices = flecs_sparse_ids(src); + + ecs_size_t size = src->size; + int32_t i, count = src->count; + + for (i = 0; i < count - 1; i ++) { + uint64_t index = indices[i]; + void *src_ptr = _flecs_sparse_get(src, size, index); + void *dst_ptr = _flecs_sparse_ensure(dst, size, index); + flecs_sparse_set_generation(dst, index); + ecs_os_memcpy(dst_ptr, src_ptr, size); + } + + set_id(dst, get_id(src)); + + ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); +} + +ecs_sparse_t* flecs_sparse_copy( + const ecs_sparse_t *src) +{ + if (!src) { + return NULL; + } + + ecs_sparse_t *dst = _flecs_sparse_new(src->size); + sparse_copy(dst, src); + + return dst; +} + +void flecs_sparse_restore( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); + dst->count = 1; + if (src) { + sparse_copy(dst, src); + } +} + +void flecs_sparse_memory( + ecs_sparse_t *sparse, + int32_t *allocd, + int32_t *used) +{ + (void)sparse; + (void)allocd; + (void)used; +} + +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size) +{ + return _flecs_sparse_new(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); +} + +#ifdef FLECS_DEPRECATED + + +int32_t ecs_count_w_filter( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + return ecs_count_filter(world, filter); +} + +int32_t ecs_count_entity( + const ecs_world_t *world, + ecs_id_t entity) +{ + return ecs_count_id(world, entity); +} + +void ecs_set_component_actions_w_entity( + ecs_world_t *world, + ecs_id_t id, + EcsComponentLifecycle *actions) +{ + ecs_set_component_actions_w_id(world, id, actions); +} + +ecs_entity_t ecs_new_w_entity( + ecs_world_t *world, + ecs_id_t id) +{ + return ecs_new_w_id(world, id); +} + +const ecs_entity_t* ecs_bulk_new_w_entity( + ecs_world_t *world, + ecs_id_t id, + int32_t count) +{ + return ecs_bulk_new_w_id(world, id, count); +} + +void ecs_enable_component_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + ecs_enable_component_w_id(world, entity, id, enable); +} + +bool ecs_is_component_enabled_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_is_component_enabled_w_id(world, entity, id); +} + +const void* ecs_get_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_get_id(world, entity, id); +} + +const void* ecs_get_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_get_id(world, entity, id); +} + +const void* ecs_get_ref_w_entity( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_get_ref_w_id(world, ref, entity, id); +} + +void* ecs_get_mut_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added) +{ + return ecs_get_mut_id(world, entity, id, is_added); +} + +void* ecs_get_mut_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added) +{ + return ecs_get_mut_id(world, entity, id, is_added); +} + +void ecs_modified_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_modified_id(world, entity, id); +} + +void ecs_modified_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_modified_id(world, entity, id); +} + +ecs_entity_t ecs_set_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) +{ + return ecs_set_id(world, entity, id, size, ptr); +} + +bool ecs_has_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_has_id(world, entity, id); +} + +size_t ecs_entity_str( + const ecs_world_t *world, + ecs_id_t entity, + char *buffer, + size_t buffer_len) +{ + return ecs_id_str(world, entity, buffer, buffer_len); +} + +ecs_entity_t ecs_get_parent_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + (void)id; + return ecs_get_object(world, entity, EcsChildOf, 0); +} + +int32_t ecs_get_thread_index( + const ecs_world_t *world) +{ + return ecs_get_stage_id(world); +} + +void ecs_add_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_add_id(world, entity, id); +} + +void ecs_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_remove_id(world, entity, id); +} + +void ecs_dim_type( + ecs_world_t *world, + ecs_type_t type, + int32_t entity_count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + if (type) { + ecs_table_t *table = ecs_table_from_type(world, type); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = flecs_table_get_or_create_data(table); + flecs_table_set_size(world, table, data, entity_count); + } +} + +ecs_type_t ecs_type_from_entity( + ecs_world_t *world, + ecs_entity_t entity) +{ + return ecs_type_from_id(world, entity); +} + +ecs_entity_t ecs_type_to_entity( + const ecs_world_t *world, + ecs_type_t type) +{ + return ecs_type_to_id(world, type); +} + +bool ecs_type_has_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity) +{ + return ecs_type_has_id(world, type, entity, false); +} + +bool ecs_type_owns_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned) +{ + return ecs_type_has_id(world, type, entity, owned); +} + +ecs_type_t ecs_column_type( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->types != NULL, ECS_INTERNAL_ERROR, NULL); + return it->types[index - 1]; +} + +int32_t ecs_column_index_from_name( + const ecs_iter_t *it, + const char *name) +{ + if (it->query) { + ecs_term_t *terms = it->query->filter.terms; + int32_t i, count = it->query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->name) { + if (!strcmp(name, term->name)) { + return i + 1; + } + } + } + } + + return 0; +} + +void* ecs_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column) +{ + return ecs_term_w_size(it, size, column); +} + +bool ecs_is_owned( + const ecs_iter_t *it, + int32_t column) +{ + return ecs_term_is_owned(it, column); +} + +bool ecs_is_readonly( + const ecs_iter_t *it, + int32_t column) +{ + return ecs_term_is_readonly(it, column); +} + +ecs_entity_t ecs_column_source( + const ecs_iter_t *it, + int32_t index) +{ + return ecs_term_source(it, index); +} + +ecs_id_t ecs_column_entity( + const ecs_iter_t *it, + int32_t index) +{ + return ecs_term_id(it, index); +} + +size_t ecs_column_size( + const ecs_iter_t *it, + int32_t index) +{ + return ecs_term_size(it, index); +} + +int32_t ecs_table_component_index( + const ecs_iter_t *it, + ecs_entity_t component) +{ + return ecs_iter_find_column(it, component); +} + +void* ecs_table_column( + const ecs_iter_t *it, + int32_t column_index) +{ + return ecs_iter_column_w_size(it, 0, column_index); +} + +size_t ecs_table_column_size( + const ecs_iter_t *it, + int32_t column_index) +{ + return ecs_iter_column_size(it, column_index); +} + +ecs_query_t* ecs_query_new( + ecs_world_t *world, + const char *expr) +{ + return ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = expr + }); +} + +void ecs_query_free( + ecs_query_t *query) +{ + ecs_query_fini(query); +} + +ecs_query_t* ecs_subquery_new( + ecs_world_t *world, + ecs_query_t *parent, + const char *expr) +{ + return ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = expr, + .parent = parent + }); +} + +#endif + +#ifdef FLECS_PLECS + + +#define TOK_NEWLINE '\n' + +static +ecs_entity_t ensure_entity( + ecs_world_t *world, + const char *path) +{ + ecs_entity_t e = ecs_lookup_fullpath(world, path); + if (!e) { + e = ecs_new_from_path(world, 0, path); + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + } + + return e; +} + +static +int create_term( + ecs_world_t *world, + ecs_term_t *term, + const char *name, + const char *expr, + int32_t column) +{ + if (!ecs_term_id_is_set(&term->pred)) { + ecs_parser_error(name, expr, column, "missing predicate in expression"); + return -1; + } + + if (!ecs_term_id_is_set(&term->args[0])) { + ecs_parser_error(name, expr, column, "missing subject in expression"); + return -1; + } + + ecs_entity_t pred = ensure_entity(world, term->pred.name); + ecs_entity_t subj = ensure_entity(world, term->args[0].name); + ecs_entity_t obj = 0; + + if (ecs_term_id_is_set(&term->args[1])) { + obj = ensure_entity(world, term->args[1].name); + } + + if (!obj) { + ecs_add_id(world, subj, pred); + } else { + ecs_add_pair(world, subj, pred, obj); + } + + return 0; +} + +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *expr) +{ + const char *ptr = expr; + ecs_term_t term = {0}; + + if (!expr) { + return 0; + } + + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (create_term(world, &term, name, expr, (int32_t)(ptr - expr))) { + return -1; + } + + ecs_term_fini(&term); + + if (ptr[0] == TOK_NEWLINE) { + ptr ++; + expr = ptr; + } + } + + return 0; +} + +int ecs_plecs_from_file( + ecs_world_t *world, + 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; + } + rewind(file); + + /* 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); + + int result = ecs_plecs_from_str(world, filename, content); + ecs_os_free(content); + return result; +error: + ecs_os_free(content); + return -1; +} + +#endif + +#ifdef FLECS_MODULE + + +char* ecs_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 = flflecs_to_i8(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 init_action, + const char *module_name, + void *handles_out, + size_t handles_size) +{ + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->name_prefix; + + char *path = ecs_module_path_from_c(module_name); + ecs_entity_t e = ecs_lookup_fullpath(world, path); + ecs_os_free(path); + + if (!e) { + ecs_trace_1("import %s", module_name); + ecs_log_push(); + + /* Load module */ + init_action(world); + + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup_fullpath(world, module_name); + ecs_assert(e != 0, ECS_MODULE_UNDEFINED, module_name); + + ecs_log_pop(); + } + + /* Copy value of module component in handles_out parameter */ + if (handles_size && handles_out) { + void *handles_ptr = ecs_get_mut_id(world, e, e, NULL); + ecs_os_memcpy(handles_out, handles_ptr, flecs_from_size_t(handles_size)); + } + + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->name_prefix = old_name_prefix; + + return e; +} + +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) +{ + ecs_assert(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + + char *import_func = (char*)module_name; /* safe */ + char *module = (char*)module_name; + + if (!ecs_os_has_modules() || !ecs_os_has_dl()) { + ecs_os_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 = flflecs_to_i8(toupper(ch)); + bptr ++; + capitalize = false; + } else { + *bptr = flflecs_to_i8(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_os_err("failed to find library file for '%s'", library_name); + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace_1("found file '%s' for library '%s'", + library_filename, library_name); + } + + ecs_os_dl_t dl = ecs_os_dlopen(library_filename); + if (!dl) { + ecs_os_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_1("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_os_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_1("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, NULL, 0); + + 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; +} + +void ecs_add_module_tag( + ecs_world_t *world, + ecs_entity_t module) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(module != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t e = module; + do { + ecs_add_id(world, e, EcsModule); + ecs_type_t type = ecs_get_type(world, e); + int32_t index = ecs_type_index_of(type, 0, + ecs_pair(EcsChildOf, EcsWildcard)); + if (index == -1) { + return; + } + + ecs_entity_t *pair = ecs_vector_get(type, ecs_id_t, index); + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); + e = ecs_pair_object(world, *pair); + } while (true); +} + +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + const char *name = desc->entity.name; + + char *module_path = ecs_module_path_from_c(name); + ecs_entity_t e = ecs_new_from_fullpath(world, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); + + ecs_component_desc_t private_desc = *desc; + private_desc.entity.entity = e; + private_desc.entity.name = NULL; + + 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); + + /* Add module tag */ + ecs_add_module_tag(world, result); + + /* Add module to itself. This way we have all the module information stored + * in a single contained entity that we can use for namespacing */ + ecs_set_id(world, result, result, desc->size, NULL); + + return result; +} + +#endif + +#ifdef FLECS_QUEUE + +struct ecs_queue_t { + ecs_vector_t *data; + int32_t index; +}; + +ecs_queue_t* _ecs_queue_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->data = _ecs_vector_new(elem_size, offset, elem_count); + result->index = 0; + return result; +} + +ecs_queue_t* _ecs_queue_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->data = _ecs_vector_from_array(elem_size, offset, elem_count, array); + result->index = 0; + return result; +} + +void* _ecs_queue_push( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset) +{ + int32_t size = ecs_vector_size(buffer->data); + int32_t count = ecs_vector_count(buffer->data); + void *result; + + if (count == buffer->index) { + result = _ecs_vector_add(&buffer->data, elem_size, offset); + } else { + result = _ecs_vector_get(buffer->data, elem_size, offset, buffer->index); + } + + buffer->index = (buffer->index + 1) % size; + + return result; +} + +void ecs_queue_free( + ecs_queue_t *buffer) +{ + ecs_vector_free(buffer->data); + ecs_os_free(buffer); +} + +void* _ecs_queue_get( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + int32_t count = ecs_vector_count(buffer->data); + int32_t size = ecs_vector_size(buffer->data); + index = ((buffer->index - count + size) + (int32_t)index) % size; + return _ecs_vector_get(buffer->data, elem_size, offset, index); +} + +void* _ecs_queue_last( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset) +{ + int32_t index = buffer->index; + if (!index) { + index = ecs_vector_size(buffer->data); + } + + return _ecs_vector_get(buffer->data, elem_size, offset, index - 1); +} + +int32_t ecs_queue_index( + ecs_queue_t *buffer) +{ + return buffer->index; +} + +int32_t ecs_queue_count( + ecs_queue_t *buffer) +{ + return ecs_vector_count(buffer->data); +} + +#endif + + +#ifdef FLECS_STATS + +#ifdef FLECS_SYSTEM +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H + + +typedef struct EcsSystem { + ecs_iter_action_t action; /* Callback to be invoked for matching it */ + + ecs_entity_t entity; /* Entity id of system, used for ordering */ + ecs_query_t *query; /* System query */ + ecs_on_demand_out_t *on_demand; /* Keep track of [out] column refs */ + ecs_system_status_action_t status_action; /* Status action */ + ecs_entity_t tick_source; /* Tick source associated with system */ + + int32_t invoke_count; /* Number of times system is invoked */ + FLECS_FLOAT time_spent; /* Time spent on running system */ + FLECS_FLOAT time_passed; /* Time passed since last invocation */ + + ecs_entity_t self; /* Entity associated with system */ + + void *ctx; /* Userdata for system */ + void *status_ctx; /* User data for status action */ + void *binding_ctx; /* Optional language binding context */ + + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t status_ctx_free; + ecs_ctx_free_t binding_ctx_free; +} EcsSystem; + +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data); + +/* Internal function to run a system */ +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param); + +#endif +#endif + +#ifdef FLECS_PIPELINE +#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 and contains + * information about the set of systems that need to be ran before a merge. */ +typedef struct ecs_pipeline_op_t { + int32_t count; /**< Number of systems to run before merge */ +} ecs_pipeline_op_t; + +typedef struct EcsPipelineQuery { + ecs_query_t *query; + ecs_query_t *build_query; + int32_t match_count; + ecs_vector_t *ops; +} EcsPipelineQuery; + +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// + +/** Update a pipeline (internal function). + * Before running a pipeline, it must be updated. During this update phase + * all systems in the pipeline are collected, ordered and sync points are + * inserted where necessary. This operation may only be called when staging is + * disabled. + * + * Because multiple threads may run a pipeline, preparing the pipeline must + * happen synchronously, which is why this function is separate from + * ecs_pipeline_run. Not running the prepare step may cause systems to not get + * ran, or ran in the wrong order. + * + * If 0 is provided for the pipeline id, the default pipeline will be ran (this + * is either the builtin pipeline or the pipeline set with set_pipeline()). + * + * @param world The world. + * @param pipeline The pipeline to run. + * @return The number of elements in the pipeline. + */ +int32_t ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame); + +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// + +void ecs_worker_begin( + ecs_world_t *world); + +bool ecs_worker_sync( + ecs_world_t *world); + +void ecs_worker_end( + ecs_world_t *world); + +void ecs_workers_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time); + +#endif +#endif + +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 _record_gauge( + ecs_gauge_t *m, + int32_t t, + float value) +{ + m->avg[t] = value; + m->min[t] = value; + m->max[t] = value; +} + +static +float _record_counter( + ecs_counter_t *m, + int32_t t, + float value) +{ + int32_t tp = t_prev(t); + float prev = m->value[tp]; + m->value[t] = value; + _record_gauge((ecs_gauge_t*)m, t, value - prev); + return value - prev; +} + +/* Macro's to silence conversion warnings without adding casts everywhere */ +#define record_gauge(m, t, value)\ + _record_gauge(m, t, (float)value) + +#define record_counter(m, t, value)\ + _record_counter(m, t, (float)value) + +static +void print_value( + const char *name, + float value) +{ + ecs_size_t len = ecs_os_strlen(name); + printf("%s: %*s %.2f\n", name, 32 - len, "", (double)value); +} + +static +void print_gauge( + const char *name, + int32_t t, + const ecs_gauge_t *m) +{ + print_value(name, m->avg[t]); +} + +static +void print_counter( + const char *name, + int32_t t, + const ecs_counter_t *m) +{ + print_value(name, m->rate.avg[t]); +} + +void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src) +{ + ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(src != NULL, ECS_INVALID_PARAMETER, NULL); + + bool min_set = false; + dst->min[t_dst] = 0; + dst->avg[t_dst] = 0; + dst->max[t_dst] = 0; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; + if (!min_set || (src->min[t] < dst->min[t_dst])) { + dst->min[t_dst] = src->min[t]; + min_set = true; + } + if ((src->max[t] > dst->max[t_dst])) { + dst->max[t_dst] = src->max[t]; + } + } +} + +void ecs_get_world_stats( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + int32_t t = s->t = t_next(s->t); + + float delta_world_time = record_counter(&s->world_time_total_raw, t, world->stats.world_time_total_raw); + record_counter(&s->world_time_total, t, world->stats.world_time_total); + record_counter(&s->frame_time_total, t, world->stats.frame_time_total); + record_counter(&s->system_time_total, t, world->stats.system_time_total); + record_counter(&s->merge_time_total, t, world->stats.merge_time_total); + + float delta_frame_count = record_counter(&s->frame_count_total, t, world->stats.frame_count_total); + record_counter(&s->merge_count_total, t, world->stats.merge_count_total); + record_counter(&s->pipeline_build_count_total, t, world->stats.pipeline_build_count_total); + record_counter(&s->systems_ran_frame, t, world->stats.systems_ran_frame); + + if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { + record_gauge( + &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); + } else { + record_gauge(&s->fps, t, 0); + } + + record_gauge(&s->entity_count, t, flecs_sparse_count(world->store.entity_index)); + record_gauge(&s->component_count, t, ecs_count_id(world, ecs_id(EcsComponent))); + record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); + record_gauge(&s->system_count, t, ecs_count_id(world, ecs_id(EcsSystem))); + + record_counter(&s->new_count, t, world->new_count); + record_counter(&s->bulk_new_count, t, world->bulk_new_count); + record_counter(&s->delete_count, t, world->delete_count); + record_counter(&s->clear_count, t, world->clear_count); + record_counter(&s->add_count, t, world->add_count); + record_counter(&s->remove_count, t, world->remove_count); + record_counter(&s->set_count, t, world->set_count); + record_counter(&s->discard_count, t, world->discard_count); + + /* Compute table statistics */ + int32_t empty_table_count = 0; + int32_t singleton_table_count = 0; + int32_t matched_table_count = 0, matched_entity_count = 0; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + int32_t entity_count = ecs_table_count(table); + + if (!entity_count) { + empty_table_count ++; + } + + /* Singleton tables are tables that have just one entity that also has + * itself in the table type. */ + if (entity_count == 1) { + ecs_data_t *data = flecs_table_get_data(table); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + if (ecs_type_has_id(world, table->type, entities[0], false)) { + singleton_table_count ++; + } + } + + /* If this table matches with queries and is not empty, increase the + * matched table & matched entity count. These statistics can be used to + * compute actual fragmentation ratio for queries. */ + int32_t queries_matched = ecs_vector_count(table->queries); + if (queries_matched && entity_count) { + matched_table_count ++; + matched_entity_count += entity_count; + } + } + + record_gauge(&s->matched_table_count, t, matched_table_count); + record_gauge(&s->matched_entity_count, t, matched_entity_count); + + record_gauge(&s->table_count, t, count); + record_gauge(&s->empty_table_count, t, empty_table_count); + record_gauge(&s->singleton_table_count, t, singleton_table_count); +} + +void ecs_get_query_stats( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + int32_t t = s->t = t_next(s->t); + + int32_t i, entity_count = 0, count = ecs_vector_count(query->tables); + ecs_matched_table_t *matched_tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + for (i = 0; i < count; i ++) { + ecs_matched_table_t *matched = &matched_tables[i]; + if (matched->table) { + entity_count += ecs_table_count(matched->table); + } + } + + record_gauge(&s->matched_table_count, t, count); + record_gauge(&s->matched_empty_table_count, t, + ecs_vector_count(query->empty_tables)); + record_gauge(&s->matched_entity_count, t, entity_count); +} + +#ifdef FLECS_SYSTEM +bool ecs_get_system_stats( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const EcsSystem *ptr = ecs_get(world, system, EcsSystem); + if (!ptr) { + return false; + } + + ecs_get_query_stats(world, ptr->query, &s->query_stats); + int32_t t = s->query_stats.t; + + record_counter(&s->time_spent, t, ptr->time_spent); + record_counter(&s->invoke_count, t, ptr->invoke_count); + record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); + record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + + return true; +} +#endif + + +#ifdef FLECS_PIPELINE + +static ecs_system_stats_t* get_system_stats( + ecs_map_t *systems, + ecs_entity_t system) +{ + ecs_assert(systems != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system); + if (!s) { + ecs_system_stats_t stats; + memset(&stats, 0, sizeof(ecs_system_stats_t)); + ecs_map_set(systems, system, &stats); + s = ecs_map_get(systems, ecs_system_stats_t, system); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + } + + return s; +} + +bool ecs_get_pipeline_stats( + const ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + if (!pq) { + return false; + } + + /* First find out how many systems are matched by the pipeline */ + ecs_iter_t it = ecs_query_iter(pq->query); + int32_t count = 0; + while (ecs_query_next(&it)) { + count += it.count; + } + + if (!s->system_stats) { + s->system_stats = ecs_map_new(ecs_system_stats_t, count); + } + + /* Also count synchronization points */ + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + count += ecs_vector_count(ops); + + /* Make sure vector is large enough to store all systems & sync points */ + ecs_vector_set_count(&s->systems, ecs_entity_t, count - 1); + ecs_entity_t *systems = ecs_vector_first(s->systems, ecs_entity_t); + + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(pq->query); + int32_t i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + int32_t i; + 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 */ + } + + ecs_system_stats_t *sys_stats = get_system_stats( + s->system_stats, it.entities[i]); + ecs_get_system_stats(world, it.entities[i], sys_stats); + } + } + + ecs_assert(i_system == (count - 1), ECS_INTERNAL_ERROR, NULL); + + return true; +} +#endif + +void ecs_dump_world_stats( + const ecs_world_t *world, + const ecs_world_stats_t *s) +{ + int32_t t = s->t; + + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + print_counter("Frame", t, &s->frame_count_total); + printf("-------------------------------------\n"); + print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); + print_counter("systems ran last frame", t, &s->systems_ran_frame); + printf("\n"); + print_value("target FPS", world->stats.target_fps); + print_value("time scale", world->stats.time_scale); + printf("\n"); + print_gauge("actual FPS", t, &s->fps); + print_counter("frame time", t, &s->frame_time_total); + print_counter("system time", t, &s->system_time_total); + print_counter("merge time", t, &s->merge_time_total); + print_counter("simulation time elapsed", t, &s->world_time_total); + printf("\n"); + print_gauge("entity count", t, &s->entity_count); + print_gauge("component count", t, &s->component_count); + print_gauge("query count", t, &s->query_count); + print_gauge("system count", t, &s->system_count); + print_gauge("table count", t, &s->table_count); + print_gauge("singleton table count", t, &s->singleton_table_count); + print_gauge("empty table count", t, &s->empty_table_count); + printf("\n"); + print_counter("deferred new operations", t, &s->new_count); + print_counter("deferred bulk_new operations", t, &s->bulk_new_count); + print_counter("deferred delete operations", t, &s->delete_count); + print_counter("deferred clear operations", t, &s->clear_count); + print_counter("deferred add operations", t, &s->add_count); + print_counter("deferred remove operations", t, &s->remove_count); + print_counter("deferred set operations", t, &s->set_count); + print_counter("discarded operations", t, &s->discard_count); + printf("\n"); +} + +#endif + +#ifdef FLECS_SNAPSHOT + + +/* World snapshot */ +struct ecs_snapshot_t { + ecs_world_t *world; + ecs_sparse_t *entity_index; + ecs_vector_t *tables; + ecs_entity_t last_id; + ecs_filter_t filter; +}; + +static +ecs_data_t* duplicate_data( + const ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *main_data) +{ + ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + + int32_t i, column_count = table->column_count; + ecs_entity_t *components = ecs_vector_first(table->type, ecs_entity_t); + + result->columns = ecs_os_memdup( + main_data->columns, ECS_SIZEOF(ecs_column_t) * column_count); + + /* Copy entities */ + result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); + ecs_entity_t *entities = ecs_vector_first(result->entities, ecs_entity_t); + + /* Copy record ptrs */ + result->record_ptrs = ecs_vector_copy(main_data->record_ptrs, ecs_record_t*); + + /* Copy each column */ + for (i = 0; i < column_count; i ++) { + ecs_entity_t component = components[i]; + ecs_column_t *column = &result->columns[i]; + + component = ecs_get_typeid(world, component); + + const ecs_type_info_t *cdata = flecs_get_c_info(world, component); + int16_t size = column->size; + int16_t alignment = column->alignment; + ecs_copy_t copy; + + if (cdata && (copy = cdata->lifecycle.copy)) { + int32_t count = ecs_vector_count(column->data); + ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, count); + ecs_vector_set_count_t(&dst_vec, size, alignment, count); + void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); + void *ctx = cdata->lifecycle.ctx; + + ecs_xtor_t ctor = cdata->lifecycle.ctor; + if (ctor) { + ctor((ecs_world_t*)world, component, entities, dst_ptr, + flecs_to_size_t(size), count, ctx); + } + + void *src_ptr = ecs_vector_first_t(column->data, size, alignment); + copy((ecs_world_t*)world, component, entities, entities, dst_ptr, + src_ptr, flecs_to_size_t(size), count, ctx); + + column->data = dst_vec; + } else { + column->data = ecs_vector_copy_t(column->data, size, alignment); + } + } + + return result; +} + +static +ecs_snapshot_t* snapshot_create( + const ecs_world_t *world, + const ecs_sparse_t *entity_index, + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_snapshot_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_snapshot_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->world = (ecs_world_t*)world; + + /* If no iterator is provided, the snapshot will be taken of the entire + * world, and we can simply copy the entity index as it will be restored + * entirely upon snapshote restore. */ + if (!iter && entity_index) { + result->entity_index = flecs_sparse_copy(entity_index); + result->tables = ecs_vector_new(ecs_table_leaf_t, 0); + } + + ecs_iter_t iter_stack; + if (!iter) { + iter_stack = ecs_filter_iter(world, NULL); + iter = &iter_stack; + next = ecs_filter_next; + } + + /* If an iterator is provided, this is a filterred snapshot. In this case we + * have to patch the entity index one by one upon restore, as we don't want + * to affect entities that were not part of the snapshot. */ + else { + result->entity_index = NULL; + } + + /* Iterate tables in iterator */ + while (next(iter)) { + ecs_table_t *t = iter->table; + + if (t->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_data_t *data = flecs_table_get_data(t); + if (!data || !data->entities || !ecs_vector_count(data->entities)) { + continue; + } + + ecs_table_leaf_t *l = ecs_vector_add(&result->tables, ecs_table_leaf_t); + l->table = t; + l->type = t->type; + l->data = duplicate_data(world, t, data); + } + + return result; +} + +/** Create a snapshot */ +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); + + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + NULL, + NULL); + + result->last_id = world->stats.last_id; + + return result; +} + +/** Create a filtered snapshot */ +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_world_t *world = iter->world; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + iter, + next); + + result->last_id = world->stats.last_id; + + return result; +} + +/** Restore a snapshot */ +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot) +{ + bool is_filtered = true; + + if (snapshot->entity_index) { + flecs_sparse_restore(world->store.entity_index, snapshot->entity_index); + flecs_sparse_free(snapshot->entity_index); + is_filtered = false; + } + + if (!is_filtered) { + world->stats.last_id = snapshot->last_id; + } + + ecs_table_leaf_t *leafs = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t l = 0, count = ecs_vector_count(snapshot->tables); + int32_t t, table_count = flecs_sparse_count(world->store.tables); + + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, t); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_table_leaf_t *leaf = NULL; + if (l < count) { + leaf = &leafs[l]; + } + + if (leaf && leaf->table == table) { + /* If the snapshot is filtered, update the entity index for the + * entities in the snapshot. If the snapshot was not filtered + * the entity index would have been replaced entirely, and this + * is not necessary. */ + if (is_filtered) { + ecs_vector_each(leaf->data->entities, ecs_entity_t, e_ptr, { + ecs_record_t *r = ecs_eis_get(world, *e_ptr); + if (r && r->table) { + ecs_data_t *data = flecs_table_get_data(r->table); + + /* Data must be not NULL, otherwise entity index could + * not point to it */ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + + /* Always delete entity, so that even if the entity is + * in the current table, there won't be duplicates */ + flecs_table_delete(world, r->table, data, row, true); + } else { + ecs_eis_set_generation(world, *e_ptr); + } + }); + + int32_t old_count = ecs_table_count(table); + int32_t new_count = flecs_table_data_count(leaf->data); + + ecs_data_t *data = flecs_table_get_data(table); + data = flecs_table_merge(world, table, table, data, leaf->data); + + /* Run OnSet systems for merged entities */ + flecs_run_set_systems(world, 0, table, data, NULL, + old_count, new_count, true); + + ecs_os_free(leaf->data->columns); + } else { + flecs_table_replace_data(world, table, leaf->data); + } + + ecs_os_free(leaf->data); + l ++; + } else { + /* If the snapshot is not filtered, the snapshot should restore the + * world to the exact state it was in. When a snapshot is filtered, + * it should only update the entities that were in the snapshot. + * If a table is found that was not in the snapshot, and the + * snapshot was not filtered, clear the table. */ + if (!is_filtered) { + /* Clear data of old table. */ + flecs_table_clear_data(world, table, flecs_table_get_data(table)); + } + } + + table->alloc_count ++; + } + + /* If snapshot was not filtered, run OnSet systems now. This cannot be done + * while restoring the snapshot, because the world is in an inconsistent + * state while restoring. When a snapshot is filtered, the world is not left + * in an inconsistent state, which makes running OnSet systems while + * restoring safe */ + if (!is_filtered) { + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, t); + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_data_t *table_data = flecs_table_get_data(table); + int32_t entity_count = flecs_table_data_count(table_data); + + flecs_run_set_systems(world, 0, table, + table_data, NULL, 0, entity_count, true); + } + } + + ecs_vector_free(snapshot->tables); + + ecs_os_free(snapshot); +} + +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot, + const ecs_filter_t *filter) +{ + ecs_snapshot_iter_t iter = { + .filter = filter ? *filter : (ecs_filter_t){0}, + .tables = snapshot->tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = snapshot->world, + .table_count = ecs_vector_count(snapshot->tables), + .iter.snapshot = iter + }; +} + +bool ecs_snapshot_next( + ecs_iter_t *it) +{ + ecs_snapshot_iter_t *iter = &it->iter.snapshot; + ecs_table_leaf_t *tables = ecs_vector_first(iter->tables, ecs_table_leaf_t); + int32_t count = ecs_vector_count(iter->tables); + int32_t i; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = tables[i].table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = tables[i].data; + + /* Table must have data or it wouldn't have been added */ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!flecs_table_match_filter(it->world, table, &iter->filter)) { + continue; + } + + it->table = table; + it->table_columns = data->columns; + it->count = flecs_table_data_count(data); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->is_valid = true; + iter->index = i + 1; + goto yield; + } + + it->is_valid = false; + return false; + +yield: + it->is_valid = true; + return true; +} + +/** Cleanup snapshot */ +void ecs_snapshot_free( + ecs_snapshot_t *snapshot) +{ + flecs_sparse_free(snapshot->entity_index); + + ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t i, count = ecs_vector_count(snapshot->tables); + for (i = 0; i < count; i ++) { + ecs_table_leaf_t *leaf = &tables[i]; + flecs_table_clear_data(snapshot->world, leaf->table, leaf->data); + ecs_os_free(leaf->data); + } + + ecs_vector_free(snapshot->tables); + ecs_os_free(snapshot); +} + +#endif + +#ifdef FLECS_BULK + + +static +void bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter, + bool is_delete) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + int32_t i, count = flecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + /* Remove entities from index */ + ecs_data_t *data = flecs_table_get_data(table); + if (!data) { + /* If table has no data, there's nothing to delete */ + continue; + } + + /* Both filters passed, clear table */ + if (is_delete) { + flecs_table_delete_entities(world, table); + } else { + flecs_table_clear_entities_silent(world, table); + } + } +} + +static +void merge_table( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + ecs_ids_t *to_add, + ecs_ids_t *to_remove) +{ + if (!dst_table->type) { + /* If this removes all components, clear table */ + flecs_table_clear_entities(world, src_table); + } else { + /* Merge table into dst_table */ + if (dst_table != src_table) { + ecs_data_t *src_data = flecs_table_get_data(src_table); + int32_t dst_count = ecs_table_count(dst_table); + int32_t src_count = ecs_table_count(src_table); + + if (to_remove && to_remove->count && src_data) { + flecs_run_remove_actions(world, src_table, + src_data, 0, src_count, to_remove); + } + + ecs_data_t *dst_data = flecs_table_get_data(dst_table); + dst_data = flecs_table_merge( + world, dst_table, src_table, dst_data, src_data); + + if (to_add && to_add->count && dst_data) { + flecs_run_add_actions(world, dst_table, dst_data, + dst_count, src_count, to_add, false, true); + } + } + } +} + +/* -- Public API -- */ + +void ecs_bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + bulk_delete(world, filter, true); +} + +void ecs_bulk_add_remove_type( + ecs_world_t *world, + ecs_type_t to_add, + ecs_type_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_add_array = flecs_type_to_ids(to_add); + ecs_ids_t to_remove_array = flecs_type_to_ids(to_remove); + + ecs_ids_t added = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count), + .count = 0 + }; + + ecs_ids_t removed = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count), + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + dst_table = flecs_table_traverse_add( + world, dst_table, &to_add_array, &added); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (table == dst_table || (!added.count && !removed.count)) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + merge_table(world, dst_table, table, &added, &removed); + added.count = 0; + removed.count = 0; + } +} + +void ecs_bulk_add_type( + ecs_world_t *world, + ecs_type_t to_add, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_add_array = flecs_type_to_ids(to_add); + ecs_ids_t added = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count), + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_add( + world, table, &to_add_array, &added); + + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!added.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, &added, NULL); + added.count = 0; + } +} + +void ecs_bulk_add_entity( + ecs_world_t *world, + ecs_entity_t to_add, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_add_array = { .array = &to_add, .count = 1 }; + + ecs_entity_t added_entity; + ecs_ids_t added = { + .array = &added_entity, + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_add( + world, table, &to_add_array, &added); + + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!added.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, &added, NULL); + added.count = 0; + } +} + +void ecs_bulk_remove_type( + ecs_world_t *world, + ecs_type_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_remove_array = flecs_type_to_ids(to_remove); + ecs_ids_t removed = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count), + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!removed.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, NULL, &removed); + removed.count = 0; + } +} + +void ecs_bulk_remove_entity( + ecs_world_t *world, + ecs_entity_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_remove_array = { .array = &to_remove, .count = 1 }; + + ecs_entity_t removed_entity; + ecs_ids_t removed = { + .array = &removed_entity, + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!removed.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, NULL, &removed); + removed.count = 0; + } +} + +#endif + +#ifdef FLECS_PARSER + + +/* TODO: after new query parser is working & code is ported to new types for + * storing terms, this code needs a big cleanup */ + +#define ECS_ANNOTATION_LENGTH_MAX (16) + +#define TOK_COLON ':' +#define TOK_AND ',' +#define TOK_OR "||" +#define TOK_NOT '!' +#define TOK_OPTIONAL '?' +#define TOK_BITWISE_OR '|' +#define TOK_NAME_SEP '.' +#define TOK_BRACKET_OPEN '[' +#define TOK_BRACKET_CLOSE ']' +#define TOK_WILDCARD '*' +#define TOK_SINGLETON '$' +#define TOK_PAREN_OPEN '(' +#define TOK_PAREN_CLOSE ')' +#define TOK_AS_ENTITY '\\' + +#define TOK_SELF "self" +#define TOK_SUPERSET "superset" +#define TOK_SUBSET "subset" +#define TOK_CASCADE_SET "cascade" +#define TOK_ALL "all" + +#define TOK_ANY "ANY" +#define TOK_OWNED "OWNED" +#define TOK_SHARED "SHARED" +#define TOK_SYSTEM "SYSTEM" +#define TOK_PARENT "PARENT" +#define TOK_CASCADE "CASCADE" + +#define TOK_ROLE_PAIR "PAIR" +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_XOR "XOR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_SWITCH "SWITCH" +#define TOK_ROLE_CASE "CASE" +#define TOK_ROLE_DISABLED "DISABLED" + +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" + +#define ECS_MAX_TOKEN_SIZE (256) + +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + +static +const char *skip_newline_and_space( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +/** Skip spaces when parsing signature */ +static +const char *skip_space( + const char *ptr) +{ + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +/* -- Private functions -- */ + +static +bool valid_token_start_char( + char ch) +{ + if (ch && (isalpha(ch) || (ch == '.') || (ch == '_') || (ch == '*') || + (ch == '0') || (ch == TOK_AS_ENTITY) || isdigit(ch))) + { + return true; + } + + return false; +} + +static +bool valid_token_char( + char ch) +{ + if (ch && (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.')) { + return true; + } + + return false; +} + +static +bool valid_operator_char( + char ch) +{ + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; + } + + return false; +} + +static +const char* parse_digit( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + char *token_out) +{ + ptr = skip_space(ptr); + char *tptr = token_out, ch = ptr[0]; + + if (!isdigit(ch)) { + ecs_parser_error(name, sig, column, + "invalid start of number '%s'", ptr); + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + return skip_space(ptr); +} + + +static +const char* parse_token( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + char *token_out) +{ + ptr = skip_space(ptr); + char *tptr = token_out, ch = ptr[0]; + + if (!valid_token_start_char(ch)) { + ecs_parser_error(name, sig, column, + "invalid start of identifier '%s'", ptr); + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + int tmpl_nesting = 0; + + for (; (ch = *ptr); ptr ++) { + if (ch == '<') { + tmpl_nesting ++; + } else if (ch == '>') { + if (!tmpl_nesting) { + break; + } + tmpl_nesting --; + } else + if (!valid_token_char(ch)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + if (tmpl_nesting != 0) { + ecs_parser_error(name, sig, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; + } + + return skip_space(ptr); +} + +static +int parse_identifier( + const char *token, + ecs_term_id_t *out) +{ + char ch = token[0]; + + const char *tptr = token; + if (ch == TOK_AS_ENTITY) { + tptr ++; + } + + out->name = ecs_os_strdup(tptr); + + if (ch == TOK_AS_ENTITY) { + out->var = EcsVarIsEntity; + } else if (ecs_identifier_is_var(tptr)) { + out->var = EcsVarIsVariable; + } + + return 0; +} + +static +ecs_entity_t parse_role( + const char *name, + const char *sig, + int64_t column, + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_ROLE_PAIR)) + { + return ECS_PAIR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { + return ECS_AND; + } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { + return ECS_OR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_XOR)) { + return ECS_XOR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { + return ECS_NOT; + } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { + return ECS_SWITCH; + } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { + return ECS_CASE; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + return ECS_OWNED; + } else if (!ecs_os_strcmp(token, TOK_ROLE_DISABLED)) { + return ECS_DISABLED; + } else { + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; + } +} + +static +ecs_oper_kind_t parse_operator( + char ch) +{ + if (ch == TOK_OPTIONAL) { + return EcsOptional; + } else if (ch == TOK_NOT) { + return EcsNot; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +static +const char* parse_annotation( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + ecs_inout_kind_t *inout_kind_out) +{ + char token[ECS_MAX_TOKEN_SIZE]; + + ptr = parse_token(name, sig, column, ptr, token); + if (!ptr) { + return NULL; + } + + if (!strcmp(token, "in")) { + *inout_kind_out = EcsIn; + } else + if (!strcmp(token, "out")) { + *inout_kind_out = EcsOut; + } else + if (!strcmp(token, "inout")) { + *inout_kind_out = EcsInOut; + } + + ptr = skip_space(ptr); + + if (ptr[0] != TOK_BRACKET_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); + return NULL; + } + + return ptr + 1; +} + +static +uint8_t parse_set_token( + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_SELF)) { + return EcsSelf; + } else if (!ecs_os_strcmp(token, TOK_SUPERSET)) { + return EcsSuperSet; + } else if (!ecs_os_strcmp(token, TOK_SUBSET)) { + return EcsSubSet; + } else if (!ecs_os_strcmp(token, TOK_CASCADE_SET)) { + return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_ALL)) { + return EcsAll; + } else { + return 0; + } +} + +static +const char* parse_set_expr( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_id_t *id) +{ + do { + uint8_t tok = parse_set_token(token); + if (!tok) { + ecs_parser_error(name, expr, column, + "invalid set token '%s'", token); + return NULL; + } + + if (id->set.mask & tok) { + ecs_parser_error(name, expr, column, + "duplicate set token '%s'", token); + return NULL; + } + + if ((tok == EcsSubSet && id->set.mask & EcsSuperSet) || + (tok == EcsSuperSet && id->set.mask & EcsSubSet)) + { + ecs_parser_error(name, expr, column, + "cannot mix superset and subset", token); + return NULL; + } + + id->set.mask |= tok; + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + + /* Relationship (overrides IsA default) */ + if (!isdigit(ptr[0]) && valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + ecs_entity_t rel = ecs_lookup_fullpath(world, token); + if (!rel) { + ecs_parser_error(name, expr, column, + "unresolved identifier '%s'", token); + return NULL; + } + + id->set.relation = rel; + + if (ptr[0] == TOK_AND) { + ptr = skip_space(ptr + 1); + } else if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, + "expected ',' or ')'"); + return NULL; + } + } + + /* Max depth of search */ + if (isdigit(ptr[0])) { + ptr = parse_digit(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + id->set.max_depth = atoi(token); + if (id->set.max_depth < 0) { + ecs_parser_error(name, expr, column, + "invalid negative depth"); + return NULL; + } + + if (ptr[0] == ',') { + ptr = skip_space(ptr + 1); + } + } + + /* If another digit is found, previous depth was min depth */ + if (isdigit(ptr[0])) { + ptr = parse_digit(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + id->set.min_depth = id->set.max_depth; + id->set.max_depth = atoi(token); + if (id->set.max_depth < 0) { + ecs_parser_error(name, expr, column, + "invalid negative depth"); + return NULL; + } + } + + if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, "expected ')'"); + return NULL; + } else { + ptr = skip_space(ptr + 1); + if (ptr[0] != TOK_PAREN_CLOSE && ptr[0] != TOK_AND) { + ecs_parser_error(name, expr, column, + "expected end of set expr"); + return NULL; + } + } + } + + /* Next token in set expression */ + if (ptr[0] == TOK_BITWISE_OR) { + ptr ++; + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + } + + /* End of set expression */ + } else if (ptr[0] == TOK_PAREN_CLOSE || ptr[0] == TOK_AND) { + break; + } + } while (true); + + if (id->set.mask & EcsCascade && !(id->set.mask & EcsSuperSet) && + !(id->set.mask & EcsSubSet)) + { + /* If cascade is used without specifying superset or subset, assume + * superset */ + id->set.mask |= EcsSuperSet; + } + + if (id->set.mask & EcsSelf && id->set.min_depth != 0) { + ecs_parser_error(name, expr, column, + "min_depth must be zero for set expression with 'self'"); + return NULL; + } + + return ptr; +} + +static +const char* parse_arguments( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_t *term) +{ + (void)column; + + int32_t arg = 0; + + do { + if (valid_token_start_char(ptr[0])) { + if (arg == 2) { + ecs_parser_error(name, expr, (ptr - expr), + "too many arguments in term"); + return NULL; + } + + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* If token is a self, superset or subset token, this is a set + * expression */ + if (!ecs_os_strcmp(token, TOK_ALL) || + !ecs_os_strcmp(token, TOK_CASCADE_SET) || + !ecs_os_strcmp(token, TOK_SELF) || + !ecs_os_strcmp(token, TOK_SUPERSET) || + !ecs_os_strcmp(token, TOK_SUBSET)) + { + ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, + token, &term->args[arg]); + if (!ptr) { + return NULL; + } + + /* Regular identifier */ + } else if (parse_identifier(token, &term->args[arg])) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + if (ptr[0] == TOK_AND) { + ptr = skip_space(ptr + 1); + + } else if (ptr[0] == TOK_PAREN_CLOSE) { + ptr = skip_space(ptr + 1); + break; + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected ',' or ')'"); + return NULL; + } + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier or set expression"); + return NULL; + } + + arg ++; + + } while (true); + + return ptr; +} + +static +const char* parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term_out) +{ + const char *ptr = expr; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + ecs_term_t term = { .move = true /* parser never owns resources */ }; + + ptr = skip_space(ptr); + + /* Inout specifiers always come first */ + if (ptr[0] == TOK_BRACKET_OPEN) { + ptr = parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); + if (!ptr) { + return NULL; + } + ptr = skip_space(ptr); + } + + if (valid_operator_char(ptr[0])) { + term.oper = parse_operator(ptr[0]); + ptr = skip_space(ptr + 1); + } + + /* If next token is the start of an identifier, it could be either a type + * role, source or component identifier */ + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is token a source identifier? */ + if (ptr[0] == TOK_COLON) { + ptr ++; + goto parse_source; + } + + /* Is token a type role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr ++; + goto parse_role; + } + + /* Is token a predicate? */ + if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_predicate; + } + + /* Next token must be a predicate */ + goto parse_predicate; + + /* If next token is the source token, this is an empty source */ + } else if (ptr[0] == TOK_COLON) { + goto empty_source; + + /* If next token is a singleton, assign identifier to pred and subject */ + } else if (ptr[0] == TOK_SINGLETON) { + ptr ++; + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_singleton; + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after singleton operator"); + return NULL; + } + + /* Pair with implicit subject */ + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + + /* Nothing else expected here */ + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ptr[0]); + return NULL; + } + +empty_source: + term.args[0].set.mask = EcsNothing; + ptr = skip_space(ptr + 1); + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_predicate; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after source operator"); + return NULL; + } + +parse_source: + if (!ecs_os_strcmp(token, TOK_PARENT)) { + term.args[0].set.mask = EcsSuperSet; + term.args[0].set.relation = EcsChildOf; + term.args[0].set.max_depth = 1; + } else if (!ecs_os_strcmp(token, TOK_SYSTEM)) { + term.args[0].name = ecs_os_strdup(name); + } else if (!ecs_os_strcmp(token, TOK_ANY)) { + term.args[0].set.mask = EcsSelf | EcsSuperSet; + term.args[0].set.relation = EcsIsA; + term.args[0].entity = EcsThis; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + term.args[0].set.mask = EcsSelf; + term.args[0].entity = EcsThis; + } else if (!ecs_os_strcmp(token, TOK_SHARED)) { + term.args[0].set.mask = EcsSuperSet; + term.args[0].set.relation = EcsIsA; + term.args[0].entity = EcsThis; + } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { + term.args[0].set.mask = EcsSuperSet | EcsCascade; + term.args[0].set.relation = EcsChildOf; + term.args[0].entity = EcsThis; + term.oper = EcsOptional; + } else { + if (parse_identifier(token, &term.args[0])) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + } + + ptr = skip_space(ptr); + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is the next token a role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr++; + goto parse_role; + } + + /* If not, it's a predicate */ + goto parse_predicate; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after source"); + return NULL; + } + +parse_role: + term.role = parse_role(name, expr, (ptr - expr), token); + if (!term.role) { + return NULL; + } + + ptr = skip_space(ptr); + + /* If next token is the source token, this is an empty source */ + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* If not, it's a predicate */ + goto parse_predicate; + + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after role"); + return NULL; + } + +parse_predicate: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = skip_space(ptr); + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + if (ptr[0] == TOK_PAREN_CLOSE) { + term.args[0].set.mask = EcsNothing; + ptr ++; + ptr = skip_space(ptr); + } else { + ptr = parse_arguments( + world, name, expr, (ptr - expr), ptr, token, &term); + } + + goto parse_done; + + } else if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_name; + } + + goto parse_done; + +parse_pair: + ptr = parse_token(name, expr, (ptr - expr), ptr + 1, token); + if (!ptr) { + return NULL; + } + + if (ptr[0] == TOK_AND) { + ptr ++; + term.args[0].entity = EcsThis; + goto parse_pair_predicate; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ptr[0]); + } + +parse_pair_predicate: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = skip_space(ptr); + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + if (ptr[0] == TOK_PAREN_CLOSE) { + ptr ++; + goto parse_pair_object; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ptr[0]); + return NULL; + } + } else if (ptr[0] == TOK_PAREN_CLOSE) { + /* No object */ + goto parse_done; + } + +parse_pair_object: + if (parse_identifier(token, &term.args[1])) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = skip_space(ptr); + goto parse_done; + +parse_singleton: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + parse_identifier(token, &term.args[0]); + goto parse_done; + +parse_name: + term.name = ecs_os_strdup(token); + ptr = skip_space(ptr); + +parse_done: + *term_out = term; + + return ptr; +} + +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_term_id_t *subj = &term->args[0]; + + bool prev_or = false; + if (ptr != expr) { + /* If this is not the start of the expression, scan back to check if + * previous token was an OR */ + const char *bptr = ptr - 1; + do { + char ch = bptr[0]; + if (isspace(ch)) { + bptr --; + continue; + } + + /* Previous token was not an OR */ + if (ch == TOK_AND) { + break; + } + + /* Previous token was an OR */ + if (ch == TOK_OR[0]) { + prev_or = true; + break; + } + + ecs_parser_error(name, expr, (ptr - expr), + "invalid preceding token"); + return NULL; + } while (true); + } + + ptr = skip_newline_and_space(ptr); + if (!ptr[0]) { + return (char*)ptr; + } + + if (ptr == expr && !strcmp(expr, "0")) { + return (char*)&ptr[1]; + } + + int32_t prev_set = subj->set.mask; + + /* Parse next element */ + ptr = parse_term(world, name, ptr, term); + if (!ptr) { + ecs_term_fini(term); + return NULL; + } + + /* Post-parse consistency checks */ + + /* If next token is OR, term is part of an OR expression */ + if (!ecs_os_strncmp(ptr, TOK_OR, 2) || prev_or) { + /* An OR operator must always follow an AND or another OR */ + if (term->oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine || with other operators"); + ecs_term_fini(term); + return NULL; + } + + term->oper = EcsOr; + } + + /* Term must either end in end of expression, AND or OR token */ + if (ptr[0] != TOK_AND && (ptr[0] != TOK_OR[0]) && (ptr[0] != '\n') && ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "expected end of expression or next term"); + ecs_term_fini(term); + return NULL; + } + + /* If the term just contained a 0, the expression has nothing. Ensure + * that after the 0 nothing else follows */ + if (!ecs_os_strcmp(term->pred.name, "0")) { + if (ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected term after 0"); + ecs_term_fini(term); + return NULL; + } + + subj->set.mask = EcsNothing; + } + + /* Cannot combine EcsNothing with operators other than AND */ + if (term->oper != EcsAnd && subj->set.mask == EcsNothing) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator for empty source"); + ecs_term_fini(term); + return NULL; + } + + /* Verify consistency of OR expression */ + if (prev_or && term->oper == EcsOr) { + /* Set expressions must be the same for all OR terms */ + if (subj->set.mask != prev_set) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine different sources in OR expression"); + ecs_term_fini(term); + return NULL; + } + + term->oper = EcsOr; + } + + /* Automatically assign This if entity is not assigned and the set is + * nothing */ + if (subj->set.mask != EcsNothing) { + if (!subj->name) { + if (!subj->entity) { + subj->entity = EcsThis; + } + } + } + + if (subj->name && !ecs_os_strcmp(subj->name, "0")) { + subj->entity = 0; + subj->set.mask = EcsNothing; + } + + /* Process role */ + if (term->role == ECS_AND) { + term->oper = EcsAndFrom; + term->role = 0; + } else if (term->role == ECS_OR) { + term->oper = EcsOrFrom; + term->role = 0; + } else if (term->role == ECS_NOT) { + term->oper = EcsNotFrom; + term->role = 0; + } + + if (ptr[0]) { + if (ptr[0] == ',') { + ptr ++; + } else if (ptr[0] == '|') { + ptr += 2; + } + } + + ptr = skip_space(ptr); + + return (char*)ptr; +} + +#endif + +#ifdef FLECS_DIRECT_ACCESS + + +/* Prefix with "da" so that they don't conflict with other get_column's */ + +static +ecs_column_t *da_get_column( + const ecs_table_t *table, + int32_t column) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(column <= table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_data_t *data = table->data; + if (data && data->columns) { + return &table->data->columns[column]; + } else { + return NULL; + } +} + +static +ecs_column_t *da_get_or_create_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column) +{ + ecs_column_t *c = da_get_column(table, column); + if (!c && (!table->data || !table->data->columns)) { + ecs_data_t *data = flecs_table_get_or_create_data(table); + flecs_init_data(world, table, data); + c = da_get_column(table, column); + } + ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); + return c; +} + +static +ecs_entity_t* get_entity_array( + ecs_table_t *table, + int32_t row) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data->entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *array = ecs_vector_first(table->data->entities, ecs_entity_t); + return &array[row]; +} + +/* -- Public API -- */ + +ecs_type_t ecs_table_get_type( + const ecs_table_t *table) +{ + return table->type; +} + +ecs_record_t* ecs_record_find( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_record_t *r = ecs_eis_get(world, entity); + if (r) { + return r; + } else { + return NULL; + } +} + +ecs_record_t* ecs_record_ensure( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_record_t *r = ecs_eis_ensure(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + return r; +} + +ecs_record_t ecs_table_insert( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record) +{ + ecs_data_t *data = flecs_table_get_or_create_data(table); + int32_t index = flecs_table_append(world, table, data, entity, record, true); + if (record) { + record->table = table; + record->row = index + 1; + } + return (ecs_record_t){table, index + 1}; +} + +int32_t ecs_table_find_column( + const ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_type_index_of(table->type, 0, component); +} + +ecs_vector_t* ecs_table_get_column( + const ecs_table_t *table, + int32_t column) +{ + ecs_column_t *c = da_get_column(table, column); + return c ? c->data : NULL; +} + +ecs_vector_t* ecs_table_set_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t* vector) +{ + ecs_column_t *c = da_get_or_create_column(world, table, column); + if (vector) { + ecs_vector_assert_size(vector, c->size); + } else { + ecs_vector_t *entities = ecs_table_get_entities(table); + if (entities) { + int32_t count = ecs_vector_count(entities); + vector = ecs_table_get_column(table, column); + if (!vector) { + vector = ecs_vector_new_t(c->size, c->alignment, count); + } else { + ecs_vector_set_count_t(&vector, c->size, c->alignment, count); + } + } + } + c->data = vector; + + return vector; +} + +ecs_vector_t* ecs_table_get_entities( + const ecs_table_t *table) +{ + ecs_data_t *data = table->data; + if (!data) { + return NULL; + } + + return data->entities; +} + +ecs_vector_t* ecs_table_get_records( + const ecs_table_t *table) +{ + ecs_data_t *data = table->data; + if (!data) { + return NULL; + } + + return data->record_ptrs; +} + +void ecs_table_set_entities( + ecs_table_t *table, + ecs_vector_t *entities, + ecs_vector_t *records) +{ + ecs_vector_assert_size(entities, sizeof(ecs_entity_t)); + ecs_vector_assert_size(records, sizeof(ecs_record_t*)); + ecs_assert(ecs_vector_count(entities) == ecs_vector_count(records), + ECS_INVALID_PARAMETER, NULL); + + ecs_data_t *data = table->data; + if (!data) { + data = flecs_table_get_or_create_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + } + + data->entities = entities; + data->record_ptrs = records; +} + +void ecs_records_clear( + ecs_vector_t *records) +{ + int32_t i, count = ecs_vector_count(records); + ecs_record_t **r = ecs_vector_first(records, ecs_record_t*); + + for (i = 0; i < count; i ++) { + r[i]->table = NULL; + if (r[i]->row < 0) { + r[i]->row = -1; + } else { + r[i]->row = 0; + } + } +} + +void ecs_records_update( + ecs_world_t *world, + ecs_vector_t *entities, + ecs_vector_t *records, + ecs_table_t *table) +{ + int32_t i, count = ecs_vector_count(records); + ecs_entity_t *e = ecs_vector_first(entities, ecs_entity_t); + ecs_record_t **r = ecs_vector_first(records, ecs_record_t*); + + for (i = 0; i < count; i ++) { + r[i] = ecs_record_ensure(world, e[i]); + ecs_assert(r[i] != NULL, ECS_INTERNAL_ERROR, NULL); + + r[i]->table = table; + r[i]->row = i + 1; + } +} + +void ecs_table_delete_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector) +{ + if (!vector) { + vector = ecs_table_get_column(table, column); + if (!vector) { + return; + } + + ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + table->data->columns[column].data = NULL; + } + + ecs_column_t *c = da_get_or_create_column(world, table, column); + ecs_vector_assert_size(vector, c->size); + + ecs_type_info_t *c_info = table->c_info[column]; + ecs_xtor_t dtor; + if (c_info && (dtor = c_info->lifecycle.dtor)) { + ecs_entity_t *entities = get_entity_array(table, 0); + int16_t alignment = c->alignment; + int32_t count = ecs_vector_count(vector); + void *ptr = ecs_vector_first_t(vector, c->size, alignment); + dtor(world, c_info->component, entities, ptr, flecs_to_size_t(c->size), + count, c_info->lifecycle.ctx); + } + + if (c->data == vector) { + c->data = NULL; + } + + ecs_vector_free(vector); +} + +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_column(table, column); + if (!c) { + return NULL; + } + + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + void *array = ecs_vector_first_t(c->data, c->size, c->alignment); + bool is_watched; + int32_t row = flecs_record_to_row(r->row, &is_watched); + return ECS_OFFSET(array, size * row); +} + +void ecs_record_copy_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + const void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_type_info_t *c_info = table->c_info[column]; + ecs_copy_t copy; + if (c_info && (copy = c_info->lifecycle.copy)) { + ecs_entity_t *entities = get_entity_array(table, row); + copy(world, c_info->component, entities, entities, ptr, value, c_size, + count, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, value, size * count); + } +} + +void ecs_record_copy_pod_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + const void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + (void)c_size; + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_os_memcpy(ptr, value, size * count); +} + +void ecs_record_move_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_type_info_t *c_info = table->c_info[column]; + ecs_move_t move; + if (c_info && (move = c_info->lifecycle.move)) { + ecs_entity_t *entities = get_entity_array(table, row); + move(world, c_info->component, entities, entities, ptr, value, c_size, + count, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, value, size * count); + } +} + +#endif + +/* Roles */ +const ecs_id_t ECS_CASE = (ECS_ROLE | (0x7Cull << 56)); +const ecs_id_t ECS_SWITCH = (ECS_ROLE | (0x7Bull << 56)); +const ecs_id_t ECS_PAIR = (ECS_ROLE | (0x7Aull << 56)); +const ecs_id_t ECS_OWNED = (ECS_ROLE | (0x75ull << 56)); +const ecs_id_t ECS_DISABLED = (ECS_ROLE | (0x74ull << 56)); + +/* Core scopes & entities */ +const ecs_entity_t EcsWorld = ECS_HI_COMPONENT_ID + 0; +const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1; +const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2; +const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 3; +const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 4; +const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 5; +const ecs_entity_t EcsHidden = ECS_HI_COMPONENT_ID + 6; + +/* Relation properties */ +const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; +const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 11; +const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 13; +const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 14; + +/* Identifier tags */ +const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 16; + +/* Relations */ +const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 21; + +/* Events */ +const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 32; +const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 33; +const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 36; +const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 37; +const ecs_entity_t EcsOnTableNonEmpty = ECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 39; +const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 40; +const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsOnComponentLifecycle = ECS_HI_COMPONENT_ID + 42; +const ecs_entity_t EcsOnDeleteObject = ECS_HI_COMPONENT_ID + 43; + +/* Actions */ +const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; +const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; +const ecs_entity_t EcsThrow = ECS_HI_COMPONENT_ID + 52; + +/* Systems */ +const ecs_entity_t EcsOnDemand = ECS_HI_COMPONENT_ID + 60; +const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; +const ecs_entity_t EcsDisabledIntern = ECS_HI_COMPONENT_ID + 62; +const ecs_entity_t EcsInactive = ECS_HI_COMPONENT_ID + 63; +const ecs_entity_t EcsPipeline = ECS_HI_COMPONENT_ID + 64; +const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; +const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; +const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; +const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68; +const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69; +const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70; +const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71; +const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72; +const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73; +const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74; + + +/* -- Private functions -- */ + +const ecs_world_t* ecs_get_world( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + return world; + } else { + return ((ecs_stage_t*)world)->world; + } +} + +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_STAGE_MAGIC, + ECS_INTERNAL_ERROR, + NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + return &world->stage; + + } else if (world->magic == ECS_STAGE_MAGIC) { + return (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(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_STAGE_MAGIC, + ECS_INTERNAL_ERROR, + NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + return &world->stage; + + } else if (world->magic == ECS_STAGE_MAGIC) { + ecs_stage_t *stage = (ecs_stage_t*)world; + *world_ptr = stage->world; + return stage; + } + + return NULL; +} + +/* Evaluate component monitor. If a monitored entity changed it will have set a + * flag in one of the world's component monitors. Queries can register + * themselves with component monitors to determine whether they need to rematch + * with tables. */ +static +void eval_component_monitor( + ecs_world_t *world) +{ + ecs_relation_monitor_t *rm = &world->monitors; + + if (!rm->is_dirty) { + return; + } + + ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); + ecs_monitor_set_t *ms; + + while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { + if (!ms->is_dirty) { + continue; + } + + if (ms->monitors) { + ecs_map_iter_t mit = ecs_map_iter(ms->monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { + if (!m->is_dirty) { + continue; + } + + ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { + flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { + .kind = EcsQueryTableRematch + }); + }); + + m->is_dirty = false; + } + } + + ms->is_dirty = false; + } + + rm->is_dirty = false; +} + +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t relation, + ecs_entity_t id) +{ + ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + ecs_monitor_set_t *ms = ecs_map_get(world->monitors.monitor_sets, + ecs_monitor_set_t, relation); + if (ms && ms->monitors) { + ecs_monitor_t *m = ecs_map_get(ms->monitors, + ecs_monitor_t, id); + if (m) { + m->is_dirty = true; + ms->is_dirty = true; + world->monitors.is_dirty = true; + } + } +} + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t relation, + 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); + ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_monitor_set_t *ms = ecs_map_ensure( + world->monitors.monitor_sets, ecs_monitor_set_t, relation); + ecs_assert(ms != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!ms->monitors) { + ms->monitors = ecs_map_new(ecs_monitor_t, 1); + } + + ecs_monitor_t *m = ecs_map_ensure(ms->monitors, ecs_monitor_t, id); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_query_t **q = ecs_vector_add(&m->queries, ecs_query_t*); + *q = query; +} + +static +void monitors_init( + ecs_relation_monitor_t *rm) +{ + rm->monitor_sets = ecs_map_new(ecs_monitor_t, 0); + rm->is_dirty = false; +} + +static +void monitors_fini( + ecs_relation_monitor_t *rm) +{ + ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); + ecs_monitor_set_t *ms; + + while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { + if (ms->monitors) { + ecs_map_iter_t mit = ecs_map_iter(ms->monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { + ecs_vector_free(m->queries); + } + + ecs_map_free(ms->monitors); + } + } + + ecs_map_free(rm->monitor_sets); +} + +static +void init_store( + ecs_world_t *world) +{ + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + /* Initialize entity index */ + world->store.entity_index = flecs_sparse_new(ecs_record_t); + flecs_sparse_set_id_source(world->store.entity_index, &world->stats.last_id); + + /* Initialize root table */ + world->store.tables = flecs_sparse_new(ecs_table_t); + + /* Initialize table map */ + world->store.table_map = flecs_table_hashmap_new(); + + /* Initialize one root table per stage */ + flecs_init_root_table(world); +} + +static +void clean_tables( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + flecs_table_free(world, t); + } + + /* Free table types separately so that if application destructors rely on + * a type it's still valid. */ + for (i = 0; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + flecs_table_free_type(t); + } + + /* Clear the root table */ + if (count) { + flecs_table_reset(world, &world->store.root); + } +} + +static +void fini_store(ecs_world_t *world) { + clean_tables(world); + flecs_sparse_free(world->store.tables); + flecs_table_free(world, &world->store.root); + flecs_sparse_clear(world->store.entity_index); + flecs_hashmap_free(world->store.table_map); +} + +/* -- Public functions -- */ + +ecs_world_t *ecs_mini(void) { + ecs_os_init(); + + ecs_trace_1("bootstrap"); + ecs_log_push(); + + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); + } + + if (!ecs_os_has_threading()) { + ecs_trace_1("threading not available"); + } + + if (!ecs_os_has_time()) { + ecs_trace_1("time management not available"); + } + + ecs_world_t *world = ecs_os_calloc(sizeof(ecs_world_t)); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + + world->magic = ECS_WORLD_MAGIC; + world->fini_actions = NULL; + + world->type_info = flecs_sparse_new(ecs_type_info_t); + world->id_index = ecs_map_new(ecs_id_record_t, 8); + world->id_triggers = ecs_map_new(ecs_id_trigger_t, 8); + + world->queries = flecs_sparse_new(ecs_query_t); + world->triggers = flecs_sparse_new(ecs_trigger_t); + world->observers = flecs_sparse_new(ecs_observer_t); + world->fini_tasks = ecs_vector_new(ecs_entity_t, 0); + world->name_prefix = NULL; + + world->aliases = flecs_string_hashmap_new(); + world->symbols = flecs_string_hashmap_new(); + + monitors_init(&world->monitors); + + world->type_handles = ecs_map_new(ecs_entity_t, 0); + world->on_activate_components = ecs_map_new(ecs_on_demand_in_t, 0); + world->on_enable_components = ecs_map_new(ecs_on_demand_in_t, 0); + + world->worker_stages = NULL; + world->workers_waiting = 0; + world->workers_running = 0; + world->quit_workers = false; + world->is_readonly = false; + world->is_fini = false; + world->measure_frame_time = false; + world->measure_system_time = false; + world->should_quit = false; + world->locking_enabled = false; + world->pipeline = 0; + + world->frame_start_time = (ecs_time_t){0, 0}; + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } + + world->stats.target_fps = 0; + world->stats.last_id = 0; + + world->stats.delta_time_raw = 0; + world->stats.delta_time = 0; + world->stats.time_scale = 1.0; + world->stats.frame_time_total = 0; + world->stats.system_time_total = 0; + world->stats.merge_time_total = 0; + world->stats.world_time_total = 0; + world->stats.frame_count_total = 0; + world->stats.merge_count_total = 0; + world->stats.systems_ran_frame = 0; + world->stats.pipeline_build_count_total = 0; + + world->range_check_enabled = false; + + world->fps_sleep = 0; + + world->context = NULL; + + world->arg_fps = 0; + world->arg_threads = 0; + + flecs_stage_init(world, &world->stage); + ecs_set_stages(world, 1); + + init_store(world); + + flecs_bootstrap(world); + + ecs_log_pop(); + + return world; +} + +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); + +#ifdef FLECS_MODULE_H + ecs_trace_1("import builtin modules"); + ecs_log_push(); +#ifdef FLECS_SYSTEM_H + ECS_IMPORT(world, FlecsSystem); +#endif +#ifdef FLECS_PIPELINE_H + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER_H + ECS_IMPORT(world, FlecsTimer); +#endif + ecs_log_pop(); +#endif + + return world; +} + +#define ARG(short, long, action)\ + if (i < argc) {\ + if (argv[i][0] == '-') {\ + if (argv[i][1] == '-') {\ + if (long && !strcmp(&argv[i][2], long ? long : "")) {\ + action;\ + parsed = true;\ + }\ + } else {\ + if (short && argv[i][1] == short) {\ + action;\ + parsed = true;\ + }\ + }\ + }\ + } + +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) +{ + (void)argc; + (void)argv; + return ecs_init(); +} + +void ecs_quit( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + world->should_quit = true; +} + +bool ecs_should_quit( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->should_quit; +} + +static +void on_demand_in_map_fini( + ecs_map_t *map) +{ + ecs_map_iter_t it = ecs_map_iter(map); + ecs_on_demand_in_t *elem; + + while ((elem = ecs_map_next(&it, ecs_on_demand_in_t, NULL))) { + ecs_vector_free(elem->systems); + } + + ecs_map_free(map); +} + +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + /* If no id is specified, broadcast to all tables */ + if (!id) { + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_notify(world, table, event); + } + + /* If id is specified, only broadcast to tables with id */ + } else { + ecs_id_record_t *r = flecs_get_id_record(world, id); + if (!r) { + return; + } + + ecs_table_record_t *tr; + ecs_map_iter_t it = ecs_map_iter(r->table_index); + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + flecs_table_notify(world, tr->table, event); + } + } +} + +static +void default_ctor( + ecs_world_t *world, ecs_entity_t component, const ecs_entity_t *entity_ptr, + void *ptr, size_t size, int32_t count, void *ctx) +{ + (void)world; (void)component; (void)entity_ptr; (void)ctx; + ecs_os_memset(ptr, 0, flecs_from_size_t(size) * count); +} + +static +void default_copy_ctor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, const void *src_ptr, + size_t size, int32_t count, void *ctx) +{ + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->copy(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); +} + +static +void default_move_ctor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); +} + +static +void default_ctor_w_move_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} + +static +void default_move_ctor_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->move_ctor(world, component, callbacks, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} + +static +void default_move( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->move(world, component, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); +} + +static +void default_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + (void)callbacks; + (void)src_entity; + + /* 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. */ + callbacks->dtor(world, component, dst_entity, dst_ptr, size, count, ctx); + ecs_os_memcpy(dst_ptr, src_ptr, flecs_from_size_t(size) * count); +} + +static +void default_move_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + /* 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. */ + callbacks->move(world, component, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} + +void ecs_set_component_actions_w_id( + ecs_world_t *world, + ecs_entity_t component, + EcsComponentLifecycle *lifecycle) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + + const EcsComponent *component_ptr = ecs_get(world, component, EcsComponent); + + /* Cannot register lifecycle actions for things that aren't a component */ + ecs_assert(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Cannot register lifecycle actions for components with size 0 */ + ecs_assert(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_type_info_t *c_info = flecs_get_or_create_c_info(world, component); + ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL); + + if (c_info->lifecycle_set) { + ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL); + ecs_assert(c_info->lifecycle.ctor == lifecycle->ctor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.dtor == lifecycle->dtor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.copy == lifecycle->copy, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.move == lifecycle->move, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + } else { + c_info->component = component; + c_info->lifecycle = *lifecycle; + c_info->lifecycle_set = true; + c_info->size = component_ptr->size; + c_info->alignment = component_ptr->alignment; + + /* If no constructor is set, invoking any of the other lifecycle actions + * is not safe as they will potentially access uninitialized memory. For + * ease of use, if no constructor is specified, set a default one that + * initializes the component to 0. */ + if (!lifecycle->ctor && + (lifecycle->dtor || lifecycle->copy || lifecycle->move)) + { + c_info->lifecycle.ctor = default_ctor; + } + + /* Set default copy ctor, move ctor and merge */ + if (lifecycle->copy && !lifecycle->copy_ctor) { + c_info->lifecycle.copy_ctor = default_copy_ctor; + } + + if (lifecycle->move && !lifecycle->move_ctor) { + c_info->lifecycle.move_ctor = default_move_ctor; + } + + if (!lifecycle->ctor_move_dtor) { + if (lifecycle->move) { + if (lifecycle->dtor) { + if (lifecycle->move_ctor) { + /* If an explicit move ctor has been set, use callback + * that uses the move ctor vs. using a ctor+move */ + c_info->lifecycle.ctor_move_dtor = + default_move_ctor_w_dtor; + } else { + /* If no explicit move_ctor has been set, use + * combination of ctor + move + dtor */ + c_info->lifecycle.ctor_move_dtor = + default_ctor_w_move_w_dtor; + } + } else { + /* If no dtor has been set, this is just a move ctor */ + c_info->lifecycle.ctor_move_dtor = + c_info->lifecycle.move_ctor; + } + } + } + + if (!lifecycle->move_dtor) { + if (lifecycle->move) { + if (lifecycle->dtor) { + c_info->lifecycle.move_dtor = default_move_w_dtor; + } else { + c_info->lifecycle.move_dtor = default_move; + } + } else { + if (lifecycle->dtor) { + c_info->lifecycle.move_dtor = default_dtor; + } + } + } + + /* Broadcast to all tables since we need to register a ctor for every + * table that uses the component as itself, as predicate or as object. + * The latter is what makes selecting the right set of tables complex, + * as it depends on the predicate of a pair whether the object is used + * as the component type or not. + * A more selective approach requires a more expressive notification + * framework. */ + flecs_notify_tables(world, 0, &(ecs_table_event_t) { + .kind = EcsTableComponentInfo, + .component = component + }); + } +} + +bool ecs_component_has_actions( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + + const ecs_type_info_t *c_info = flecs_get_c_info(world, component); + return (c_info != NULL) && c_info->lifecycle_set; +} + +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +} + +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_action_elem_t *elem = ecs_vector_add(&stage->post_frame_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +} + +/* Unset data in tables */ +static +void fini_unset_tables( + ecs_world_t *world) +{ + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_remove_actions(world, table); + } +} + +/* Invoke fini actions */ +static +void fini_actions( + ecs_world_t *world) +{ + ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { + elem->action(world, elem->ctx); + }); + + ecs_vector_free(world->fini_actions); +} + +/* Cleanup component lifecycle callbacks & systems */ +static +void fini_component_lifecycle( + ecs_world_t *world) +{ + flecs_sparse_free(world->type_info); +} + +/* Cleanup queries */ +static +void fini_queries( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->queries); + for (i = 0; i < count; i ++) { + ecs_query_t *query = flecs_sparse_get_dense(world->queries, ecs_query_t, 0); + ecs_query_fini(query); + } + flecs_sparse_free(world->queries); +} + +static +void fini_observers( + ecs_world_t *world) +{ + flecs_sparse_free(world->observers); +} + +/* Cleanup stages */ +static +void fini_stages( + ecs_world_t *world) +{ + flecs_stage_deinit(world, &world->stage); + ecs_set_stages(world, 0); +} + +/* Cleanup id index */ +static +void fini_id_index( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(world->id_index); + ecs_id_record_t *r; + while ((r = ecs_map_next(&it, ecs_id_record_t, NULL))) { + ecs_map_free(r->table_index); + } + + ecs_map_free(world->id_index); +} + +static +void fini_id_triggers( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(world->id_triggers); + ecs_id_trigger_t *t; + while ((t = ecs_map_next(&it, ecs_id_trigger_t, NULL))) { + ecs_map_free(t->on_add_triggers); + ecs_map_free(t->on_remove_triggers); + ecs_map_free(t->on_set_triggers); + ecs_map_free(t->un_set_triggers); + } + ecs_map_free(world->id_triggers); + flecs_sparse_free(world->triggers); +} + +/* Cleanup aliases & symbols */ +static +void fini_aliases( + ecs_hashmap_t *map) +{ + flecs_hashmap_iter_t it = flecs_hashmap_iter(*map); + ecs_string_t *key; + while (flecs_hashmap_next_w_key(&it, ecs_string_t, &key, ecs_entity_t)) { + ecs_os_free(key->value); + } + + flecs_hashmap_free(*map); +} + +/* Cleanup misc structures */ +static +void fini_misc( + ecs_world_t *world) +{ + on_demand_in_map_fini(world->on_activate_components); + on_demand_in_map_fini(world->on_enable_components); + ecs_map_free(world->type_handles); + ecs_vector_free(world->fini_tasks); + monitors_fini(&world->monitors); +} + +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + world->is_fini = true; + + /* Operations invoked during UnSet/OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + ecs_defer_begin(world); + + /* Run UnSet/OnRemove actions for components while the store is still + * unmodified by cleanup. */ + fini_unset_tables(world); + + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + fini_actions(world); + + /* This will destroy all entities and components. After this point no more + * user code is executed. */ + 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->stage); + + /* Entity index is kept alive until this point so that user code can do + * validity checks on entity ids, even though after store cleanup the index + * will be empty, so all entity ids are invalid. */ + flecs_sparse_free(world->store.entity_index); + + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + } + + fini_stages(world); + + fini_component_lifecycle(world); + + fini_queries(world); + + fini_observers(world); + + fini_id_index(world); + + fini_id_triggers(world); + + fini_aliases(&world->aliases); + + fini_aliases(&world->symbols); + + fini_misc(world); + + /* In case the application tries to use the memory of the freed world, this + * will trigger an assert */ + world->magic = 0; + + flecs_increase_timer_resolution(0); + + /* End of the world */ + ecs_os_free(world); + + ecs_os_fini(); + + return 0; +} + +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID); +} + +void flecs_eval_component_monitors( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + eval_component_monitor(world); +} + +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (world->stats.target_fps == 0.0f || enable) { + world->measure_frame_time = enable; + } +} + +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + world->measure_system_time = enable; +} + +/* Increase timer resolution based on target fps */ +static +void set_timer_resolution( + FLECS_FLOAT fps) +{ + if(fps >= 60.0f) flecs_increase_timer_resolution(1); + else flecs_increase_timer_resolution(0); +} + +void ecs_set_target_fps( + ecs_world_t *world, + FLECS_FLOAT fps) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (!world->arg_fps) { + ecs_measure_frame_time(world, true); + world->stats.target_fps = fps; + set_timer_resolution(fps); + } +} + +void* ecs_get_context( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->context; +} + +void ecs_set_context( + ecs_world_t *world, + void *context) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + world->context = context; +} + +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!id_end || id_end > world->stats.last_id, ECS_INVALID_PARAMETER, NULL); + + if (world->stats.last_id < id_start) { + world->stats.last_id = id_start - 1; + } + + world->stats.min_id = id_start; + world->stats.max_id = id_end; +} + +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; +} + +int32_t ecs_get_threads( + ecs_world_t *world) +{ + return ecs_vector_count(world->worker_stages); +} + +bool ecs_enable_locking( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + if (enable) { + if (!world->locking_enabled) { + world->mutex = ecs_os_mutex_new(); + world->thr_sync = ecs_os_mutex_new(); + world->thr_cond = ecs_os_cond_new(); + } + } else { + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + ecs_os_mutex_free(world->thr_sync); + ecs_os_cond_free(world->thr_cond); + } + } + + bool old = world->locking_enabled; + world->locking_enabled = enable; + return old; +} + +void ecs_lock( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->mutex); +} + +void ecs_unlock( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->mutex); +} + +void ecs_begin_wait( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_wait(world->thr_cond, world->thr_sync); +} + +void ecs_end_wait( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->thr_sync); +} + +const ecs_type_info_t * flecs_get_c_info( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL); + + return flecs_sparse_get(world->type_info, ecs_type_info_t, component); +} + +ecs_type_info_t * flecs_get_or_create_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + const ecs_type_info_t *c_info = flecs_get_c_info(world, component); + ecs_type_info_t *c_info_mut = NULL; + if (!c_info) { + c_info_mut = flecs_sparse_ensure( + world->type_info, ecs_type_info_t, component); + ecs_assert(c_info_mut != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + c_info_mut = (ecs_type_info_t*)c_info; + } + + return c_info_mut; +} + +static +FLECS_FLOAT insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + ecs_time_t start = *stop; + FLECS_FLOAT delta_time = (FLECS_FLOAT)ecs_time_measure(stop); + + if (world->stats.target_fps == (FLECS_FLOAT)0.0) { + return delta_time; + } + + FLECS_FLOAT target_delta_time = + ((FLECS_FLOAT)1.0 / (FLECS_FLOAT)world->stats.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. */ + FLECS_FLOAT sleep = target_delta_time - delta_time; + + /* Pick a sleep interval that is 20 times lower than the time one frame + * should take. This means that this function at most iterates 20 times in + * a busy loop */ + FLECS_FLOAT sleep_time = sleep / (FLECS_FLOAT)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 (sleep_time != 0) { + ecs_sleepf((double)sleep_time); + } + + ecs_time_t now = start; + delta_time = (FLECS_FLOAT)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (FLECS_FLOAT)2.0)); + + return delta_time; +} + +static +FLECS_FLOAT start_measure_frame( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + FLECS_FLOAT delta_time = 0; + + if (world->measure_frame_time || (user_delta_time == 0)) { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.sec) { + delta_time = insert_sleep(world, &t); + + ecs_time_measure(&t); + } else { + ecs_time_measure(&t); + if (world->stats.target_fps != 0) { + delta_time = (FLECS_FLOAT)1.0 / world->stats.target_fps; + } else { + /* Best guess */ + delta_time = (FLECS_FLOAT)1.0 / (FLECS_FLOAT)60.0; + } + } + + /* Keep trying while delta_time is zero */ + } while (delta_time == 0); + + world->frame_start_time = t; + + /* Keep track of total time passed in world */ + world->stats.world_time_total_raw += (FLECS_FLOAT)delta_time; + } + + return (FLECS_FLOAT)delta_time; +} + +static +void stop_measure_frame( + ecs_world_t* world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + if (world->measure_frame_time) { + ecs_time_t t = world->frame_start_time; + world->stats.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t); + } +} + +FLECS_FLOAT ecs_frame_begin( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); + + ecs_assert(user_delta_time != 0 || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); + + if (world->locking_enabled) { + ecs_lock(world); + } + + /* Start measuring total frame time */ + FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time); + if (user_delta_time == 0) { + user_delta_time = delta_time; + } + + world->stats.delta_time_raw = user_delta_time; + world->stats.delta_time = user_delta_time * world->stats.time_scale; + + /* Keep track of total scaled time passed in world */ + world->stats.world_time_total += world->stats.delta_time; + + flecs_eval_component_monitors(world); + + return world->stats.delta_time; +} + +void ecs_frame_end( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); + + world->stats.frame_count_total ++; + + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + flecs_stage_merge_post_frame(world, stage); + }); + + if (world->locking_enabled) { + ecs_unlock(world); + + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_broadcast(world->thr_cond); + ecs_os_mutex_unlock(world->thr_sync); + } + + stop_measure_frame(world); +} + +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return &world->stats; +} + +void flecs_notify_queries( + ecs_world_t *world, + ecs_query_event_t *event) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + int32_t i, count = flecs_sparse_count(world->queries); + for (i = 0; i < count; i ++) { + flecs_query_notify(world, + flecs_sparse_get_dense(world->queries, ecs_query_t, i), event); + } +} + +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + /* Notify queries that table is to be removed */ + flecs_notify_queries( + world, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + + uint64_t id = table->id; + + /* Free resources associated with table */ + flecs_table_free(world, table); + flecs_table_free_type(table); + + /* Remove table from sparse set */ + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + flecs_sparse_remove(world->store.tables, id); +} + +static +void register_table_for_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + ecs_id_record_t *r = flecs_ensure_id_record(world, id); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!r->table_index) { + r->table_index = ecs_map_new(ecs_table_record_t, 1); + } + + ecs_table_record_t *tr = ecs_map_ensure( + r->table_index, ecs_table_record_t, table->id); + + /* A table can be registered for the same entity multiple times if this is + * a trait. In that case make sure the column with the first occurrence is + * registered with the index */ + if (!tr->table || column < tr->column) { + tr->table = table; + tr->column = column; + tr->count = 1; + } else { + tr->count ++; + } + + char buf[255]; ecs_id_str(world, id, buf, 255); + + /* Set flags if triggers are registered for table */ + if (!(table->flags & EcsTableIsDisabled)) { + if (flecs_triggers_get(world, id, EcsOnAdd)) { + table->flags |= EcsTableHasOnAdd; + } + if (flecs_triggers_get(world, id, EcsOnRemove)) { + table->flags |= EcsTableHasOnRemove; + } + if (flecs_triggers_get(world, id, EcsOnSet)) { + table->flags |= EcsTableHasOnSet; + } + if (flecs_triggers_get(world, id, EcsUnSet)) { + table->flags |= EcsTableHasUnSet; + } + } +} + +static +void unregister_table_for_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_record_t *r = flecs_get_id_record(world, id); + if (!r || !r->table_index) { + return; + } + + ecs_map_remove(r->table_index, table->id); + if (!ecs_map_count(r->table_index)) { + flecs_clear_id_record(world, id); + } +} + +static +void do_register_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column, + bool unregister) +{ + if (unregister) { + unregister_table_for_id(world, table, id); + } else { + register_table_for_id(world, table, id, column); + } +} + +static +void do_register_each_id( + ecs_world_t *world, + ecs_table_t *table, + bool unregister) +{ + int32_t i, count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + bool has_childof = false; + + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; + + /* This check ensures that legacy CHILDOF works */ + if (ECS_HAS_RELATION(id, EcsChildOf)) { + has_childof = true; + } + + do_register_id(world, table, id, i, unregister); + do_register_id(world, table, EcsWildcard, i, unregister); + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t pred_w_wildcard = ecs_pair( + ECS_PAIR_RELATION(id), EcsWildcard); + do_register_id(world, table, pred_w_wildcard, i, unregister); + + ecs_entity_t obj_w_wildcard = ecs_pair( + EcsWildcard, ECS_PAIR_OBJECT(id)); + do_register_id(world, table, obj_w_wildcard, i, unregister); + + ecs_entity_t all_wildcard = ecs_pair(EcsWildcard, EcsWildcard); + do_register_id(world, table, all_wildcard, i, unregister); + + if (!unregister) { + flecs_set_watch(world, ecs_pair_relation(world, id)); + flecs_set_watch(world, ecs_pair_object(world, id)); + } + } else { + if (id & ECS_ROLE_MASK) { + id &= ECS_COMPONENT_MASK; + do_register_id(world, table, ecs_pair(id, EcsWildcard), + i, unregister); + } + + if (!unregister) { + flecs_set_watch(world, id); + } + } + } + + if (!has_childof) { + do_register_id(world, table, ecs_pair(EcsChildOf, 0), 0, unregister); + } +} + +void flecs_register_table( + ecs_world_t *world, + ecs_table_t *table) +{ + do_register_each_id(world, table, false); +} + +void flecs_unregister_table( + ecs_world_t *world, + ecs_table_t *table) +{ + /* Remove table from id indices */ + do_register_each_id(world, table, true); + + /* Remove table from table map */ + ecs_ids_t key = { + .array = ecs_vector_first(table->type, ecs_id_t), + .count = ecs_vector_count(table->type) + }; + + flecs_hashmap_remove(world->store.table_map, &key, ecs_table_t*); +} + +ecs_id_record_t* flecs_ensure_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + return ecs_map_ensure(world->id_index, ecs_id_record_t, id); +} + +ecs_id_record_t* flecs_get_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + return ecs_map_get(world->id_index, ecs_id_record_t, id); +} + +ecs_table_record_t* flecs_get_table_record( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_record_t* idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + return ecs_map_get(idr->table_index, ecs_table_record_t, table->id); +} + +void flecs_clear_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *r = flecs_get_id_record(world, id); + if (!r) { + return; + } + + ecs_map_free(r->table_index); + ecs_map_remove(world->id_index, id); +} + +#ifdef FLECS_SANITIZE +static +void verify_nodes( + flecs_switch_header_t *hdr, + flecs_switch_node_t *nodes) +{ + if (!hdr) { + return; + } + + int32_t prev = -1, elem = hdr->element, count = 0; + while (elem != -1) { + ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); + prev = elem; + elem = nodes[elem].next; + count ++; + } + + ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); +} +#else +#define verify_nodes(hdr, nodes) +#endif + +static +flecs_switch_header_t *get_header( + const ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } + + value = (uint32_t)value; + + ecs_assert(value >= sw->min, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value <= sw->max, ECS_INTERNAL_ERROR, NULL); + + uint64_t index = value - sw->min; + + return &sw->headers[index]; +} + +static +void remove_node( + flecs_switch_header_t *hdr, + flecs_switch_node_t *nodes, + flecs_switch_node_t *node, + int32_t element) +{ + ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); + + /* Update previous node/header */ + if (hdr->element == element) { + ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); + /* If this is the first node, update the header */ + hdr->element = node->next; + } else { + /* If this is not the first node, update the previous node to the + * removed node's next ptr */ + ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); + flecs_switch_node_t *prev_node = &nodes[node->prev]; + prev_node->next = node->next; + } + + /* Update next node */ + int32_t next = node->next; + if (next != -1) { + ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); + /* If this is not the last node, update the next node to point to the + * removed node's prev ptr */ + flecs_switch_node_t *next_node = &nodes[next]; + next_node->prev = node->prev; + } + + /* Decrease count of current header */ + hdr->count --; + ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); +} + +ecs_switch_t* flecs_switch_new( + uint64_t min, + uint64_t max, + int32_t elements) +{ + ecs_assert(min <= max, ECS_INVALID_PARAMETER, NULL); + + /* Min must be larger than 0, as 0 is an invalid entity id, and should + * therefore never occur as case id */ + ecs_assert(min > 0, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); + result->min = (uint32_t)min; + result->max = (uint32_t)max; + + int32_t count = (int32_t)(max - min) + 1; + result->headers = ecs_os_calloc(ECS_SIZEOF(flecs_switch_header_t) * count); + result->nodes = ecs_vector_new(flecs_switch_node_t, elements); + result->values = ecs_vector_new(uint64_t, elements); + + int64_t i; + for (i = 0; i < count; i ++) { + result->headers[i].element = -1; + result->headers[i].count = 0; + } + + flecs_switch_node_t *nodes = ecs_vector_first( + result->nodes, flecs_switch_node_t); + uint64_t *values = ecs_vector_first( + result->values, uint64_t); + + for (i = 0; i < elements; i ++) { + nodes[i].prev = -1; + nodes[i].next = -1; + values[i] = 0; + } + + return result; +} + +void flecs_switch_free( + ecs_switch_t *sw) +{ + ecs_os_free(sw->headers); + ecs_vector_free(sw->nodes); + ecs_vector_free(sw->values); + ecs_os_free(sw); +} + +void flecs_switch_add( + ecs_switch_t *sw) +{ + flecs_switch_node_t *node = ecs_vector_add(&sw->nodes, flecs_switch_node_t); + uint64_t *value = ecs_vector_add(&sw->values, uint64_t); + node->prev = -1; + node->next = -1; + *value = 0; +} + +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count == count) { + return; + } + + ecs_vector_set_count(&sw->nodes, flecs_switch_node_t, count); + ecs_vector_set_count(&sw->values, uint64_t, count); + + flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + + int32_t i; + for (i = old_count; i < count; i ++) { + flecs_switch_node_t *node = &nodes[i]; + node->prev = -1; + node->next = -1; + values[i] = 0; + } +} + +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count >= count) { + return; + } + + flecs_switch_set_count(sw, count); +} + +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + flecs_switch_set_count(sw, old_count + count); +} + +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t cur_value = values[element]; + + /* If the node is already assigned to the value, nothing to be done */ + if (cur_value == value) { + return; + } + + flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + flecs_switch_node_t *node = &nodes[element]; + + flecs_switch_header_t *cur_hdr = get_header(sw, cur_value); + flecs_switch_header_t *dst_hdr = get_header(sw, value); + + verify_nodes(cur_hdr, nodes); + verify_nodes(dst_hdr, nodes); + + /* If value is not 0, and dst_hdr is NULL, then this is not a valid value + * for this switch */ + ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); + + if (cur_hdr) { + remove_node(cur_hdr, nodes, node, element); + } + + /* Now update the node itself by adding it as the first node of dst */ + node->prev = -1; + values[element] = value; + + if (dst_hdr) { + node->next = dst_hdr->element; + + /* Also update the dst header */ + int32_t first = dst_hdr->element; + if (first != -1) { + ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); + flecs_switch_node_t *first_node = &nodes[first]; + first_node->prev = element; + } + + dst_hdr->element = element; + dst_hdr->count ++; + } +} + +void flecs_switch_remove( + ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t value = values[element]; + flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + flecs_switch_node_t *node = &nodes[element]; + + /* If node is currently assigned to a case, remove it from the list */ + if (value != 0) { + flecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); + + verify_nodes(hdr, nodes); + remove_node(hdr, nodes, node, element); + } + + int32_t last_elem = ecs_vector_count(sw->nodes) - 1; + if (last_elem != element) { + flecs_switch_node_t *last = ecs_vector_last(sw->nodes, flecs_switch_node_t); + int32_t next = last->next, prev = last->prev; + if (next != -1) { + flecs_switch_node_t *n = &nodes[next]; + n->prev = element; + } + + if (prev != -1) { + flecs_switch_node_t *n = &nodes[prev]; + n->next = element; + } else { + flecs_switch_header_t *hdr = get_header(sw, values[last_elem]); + if (hdr && hdr->element != -1) { + ecs_assert(hdr->element == last_elem, + ECS_INTERNAL_ERROR, NULL); + hdr->element = element; + } + } + } + + /* Remove element from arrays */ + ecs_vector_remove(sw->nodes, flecs_switch_node_t, element); + ecs_vector_remove(sw->values, uint64_t, element); +} + +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + return values[element]; +} + +ecs_vector_t* flecs_switch_values( + const ecs_switch_t *sw) +{ + return sw->values; +} + +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value) +{ + flecs_switch_header_t *hdr = get_header(sw, value); + if (!hdr) { + return 0; + } + + return hdr->count; +} + +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2) +{ + uint64_t v1 = flecs_switch_get(sw, elem_1); + uint64_t v2 = flecs_switch_get(sw, elem_2); + + flecs_switch_set(sw, elem_2, v1); + flecs_switch_set(sw, elem_1, v2); +} + +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert((uint32_t)value <= sw->max, ECS_INVALID_PARAMETER, NULL); + ecs_assert((uint32_t)value >= sw->min, ECS_INVALID_PARAMETER, NULL); + + flecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + + return hdr->element; +} + +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + flecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, flecs_switch_node_t); + + return nodes[element].next; +} + +#ifndef _MSC_VER +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +/* See explanation below. The hashing function may read beyond the memory passed + * into the hashing function, but only at word boundaries. This should be safe, + * but trips up address sanitizers and valgrind. + * This ensures clean valgrind logs in debug mode & the best perf in release */ +#ifndef NDEBUG +#define VALGRIND +#endif + +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + http://burtleburtle.net/bob/c/lookup3.c +------------------------------------------------------------------------------- +*/ + +#ifdef _MSC_VER +//FIXME +#else +#include <sys/param.h> /* attempt to define endianness */ +#endif +#ifdef linux +# include <endian.h> /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +#else +# define HASH_LITTLE_ENDIAN 0 +#endif + +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ +static +void hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + const uint8_t *k8; + (void)k8; + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + + final(a,b,c); + *pc=c; *pb=b; +} + +uint64_t flecs_hash( + const void *data, + ecs_size_t length) +{ + uint32_t h_1 = 0; + uint32_t h_2 = 0; + + hashlittle2( + data, + flecs_to_size_t(length), + &h_1, + &h_2); + + return h_1 | ((uint64_t)h_2 << 32); +} + + +static +int resolve_identifier( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_id_t *identifier) +{ + if (!identifier->name) { + return 0; + } + + if (identifier->var == EcsVarDefault) { + if (ecs_identifier_is_var(identifier->name)) { + identifier->var = EcsVarIsVariable; + } + } + + if (identifier->var != EcsVarIsVariable) { + if (ecs_identifier_is_0(identifier->name)) { + identifier->entity = 0; + } else { + ecs_entity_t e = ecs_lookup_symbol(world, identifier->name, true); + if (!e) { + ecs_parser_error(name, expr, 0, + "unresolved identifier '%s'", identifier->name); + return -1; + } + + /* Use OR, as entity may have already been populated with role */ + identifier->entity = e; + } + } + + return 0; +} + +bool ecs_identifier_is_0( + const char *id) +{ + return id[0] == '0' && !id[1]; +} + +bool ecs_identifier_is_var( + const char *id) +{ + if (id[0] == '_') { + return true; + } + + if (isdigit(id[0])) { + return false; + } + + const char *ptr; + char ch; + for (ptr = id; (ch = *ptr); ptr ++) { + if (!isupper(ch) && ch != '_' && !isdigit(ch)) { + return false; + } + } + + return true; +} + +static +int term_resolve_ids( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term) +{ + if (resolve_identifier(world, name, expr, &term->pred)) { + return -1; + } + if (resolve_identifier(world, name, expr, &term->args[0])) { + return -1; + } + if (resolve_identifier(world, name, expr, &term->args[1])) { + return -1; + } + + if (term->args[1].entity || term->role == ECS_PAIR) { + /* Both the relation and object must be set */ + ecs_assert(term->pred.entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term->args[1].entity != 0, ECS_INVALID_PARAMETER, NULL); + term->id = ecs_pair(term->pred.entity, term->args[1].entity); + } else { + term->id = term->pred.entity; + } + + return 0; +} + +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) +{ + if (id == pattern) { + return true; + } + + if (ECS_HAS_ROLE(pattern, PAIR)) { + if (!ECS_HAS_ROLE(id, PAIR)) { + return false; + } + + ecs_entity_t id_rel = ECS_PAIR_RELATION(id); + ecs_entity_t id_obj = ECS_PAIR_OBJECT(id); + ecs_entity_t pattern_rel = ECS_PAIR_RELATION(pattern); + ecs_entity_t pattern_obj = ECS_PAIR_OBJECT(pattern); + + ecs_assert(id_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(id_obj != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_assert(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); + + if (pattern_rel == EcsWildcard) { + if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { + return true; + } + } else if (pattern_obj == EcsWildcard) { + if (pattern_rel == id_rel) { + return true; + } + } + } else { + if ((id & ECS_ROLE_MASK) != (pattern & ECS_ROLE_MASK)) { + return false; + } + + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; + } + } + + return false; +} + +bool ecs_id_is_pair( + ecs_id_t id) +{ + return ECS_HAS_ROLE(id, PAIR); +} + +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if (id == EcsWildcard) { + return true; + } else if (ECS_HAS_ROLE(id, PAIR)) { + return ECS_PAIR_RELATION(id) == EcsWildcard || + ECS_PAIR_OBJECT(id) == EcsWildcard; + } + + return false; +} + +bool ecs_term_id_is_set( + const ecs_term_id_t *id) +{ + return id->entity != 0 || id->name != NULL; +} + +bool ecs_term_is_initialized( + const ecs_term_t *term) +{ + return term->id != 0 || ecs_term_id_is_set(&term->pred); +} + +bool ecs_term_is_trivial( + const ecs_term_t *term) +{ + if (term->inout != EcsInOutDefault) { + return false; + } + + if (term->args[0].entity != EcsThis) { + return false; + } + + if (term->args[0].set.mask != EcsDefaultSet) { + return false; + } + + if (term->oper != EcsAnd && term->oper != EcsAndFrom) { + return false; + } + + if (term->name != NULL) { + return false; + } + + return true; +} + +int ecs_term_finalize( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term) +{ + if (term->id) { + /* Allow for combining explicit object with id */ + if (term->args[1].name && !term->args[1].entity) { + if (resolve_identifier(world, name, expr, &term->args[1])) { + return -1; + } + } + + /* If other fields are set, make sure they are consistent with id */ + if (term->args[1].entity) { + ecs_assert(term->pred.entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term->id == + ecs_pair(term->pred.entity, term->args[1].entity), + ECS_INVALID_PARAMETER, NULL); + } else if (term->pred.entity) { + /* If only predicate is set (not object) it must match the id + * without any roles set. */ + ecs_assert(term->pred.entity == (term->id & ECS_COMPONENT_MASK), + ECS_INVALID_PARAMETER, NULL); + } + + /* If id is set, check for pair and derive predicate and object */ + if (ECS_HAS_ROLE(term->id, PAIR)) { + term->pred.entity = ECS_PAIR_RELATION(term->id); + term->args[1].entity = ECS_PAIR_OBJECT(term->id); + } else { + term->pred.entity = term->id & ECS_COMPONENT_MASK; + } + + if (!term->role) { + term->role = term->id & ECS_ROLE_MASK; + } else { + /* If id already has a role set it should be equal to the provided + * role */ + ecs_assert(!(term->id & ECS_ROLE_MASK) || + (term->id & ECS_ROLE_MASK) == term->role, + ECS_INVALID_PARAMETER, NULL); + } + } else { + if (term_resolve_ids(world, name, expr, term)) { + /* One or more identifiers could not be resolved */ + return -1; + } + } + + /* role field should only set role bits */ + ecs_assert(term->role == (term->role & ECS_ROLE_MASK), + ECS_INVALID_PARAMETER, NULL); + + term->id |= term->role; + + if (!term->args[0].entity && + term->args[0].set.mask != EcsNothing && + term->args[0].var != EcsVarIsVariable) + { + term->args[0].entity = EcsThis; + } + + if (term->args[0].set.mask & (EcsSuperSet | EcsSubSet)) { + if (!term->args[0].set.relation) { + term->args[0].set.relation = EcsIsA; + } + } + + return 0; +} + +ecs_term_t ecs_term_copy( + const ecs_term_t *src) +{ + ecs_term_t dst = *src; + dst.name = ecs_os_strdup(src->name); + dst.pred.name = ecs_os_strdup(src->pred.name); + dst.args[0].name = ecs_os_strdup(src->args[0].name); + dst.args[1].name = ecs_os_strdup(src->args[1].name); + return dst; +} + +ecs_term_t ecs_term_move( + ecs_term_t *src) +{ + if (src->move) { + ecs_term_t dst = *src; + src->name = NULL; + src->pred.name = NULL; + src->args[0].name = NULL; + src->args[1].name = NULL; + return dst; + } else { + return ecs_term_copy(src); + } +} + +void ecs_term_fini( + ecs_term_t *term) +{ + ecs_os_free(term->pred.name); + ecs_os_free(term->args[0].name); + ecs_os_free(term->args[1].name); + ecs_os_free(term->name); +} + +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *f) +{ + int32_t i, term_count = f->term_count, actual_count = 0; + ecs_term_t *terms = f->terms; + bool is_or = false, prev_or = false; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + if (ecs_term_finalize(world, f->name, f->expr, term)) { + return -1; + } + + is_or = term->oper == EcsOr; + actual_count += !(is_or && prev_or); + term->index = actual_count - 1; + prev_or = is_or; + + if (term->args[0].entity == EcsThis) { + f->match_this = true; + if (term->args[0].set.mask != EcsSelf) { + f->match_only_this = false; + } + } else { + f->match_only_this = false; + } + } + + f->term_count_actual = actual_count; + + return 0; +} + +int ecs_filter_init( + const ecs_world_t *stage, + ecs_filter_t *filter_out, + const ecs_filter_desc_t *desc) +{ + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(filter_out != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + int i, term_count = 0; + ecs_term_t *terms = desc->terms_buffer; + const char *name = desc->name; + const char *expr = desc->expr; + + ecs_filter_t f = { + /* Temporarily set the fields to the values provided in desc, until the + * filter has been validated. */ + .name = (char*)name, + .expr = (char*)expr + }; + + if (terms) { + terms = desc->terms_buffer; + term_count = desc->terms_buffer_count; + } else { + terms = (ecs_term_t*)desc->terms; + for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { + if (!ecs_term_is_initialized(&terms[i])) { + break; + } + + term_count ++; + } + } + + /* Temporarily set array from desc to filter, until the filter has been + * validated. */ + f.terms = terms; + f.term_count = term_count; + + if (expr) { +#ifdef FLECS_PARSER + int32_t buffer_count = 0; + + /* If terms have already been set, copy buffer to allocated one */ + if (terms && term_count) { + terms = ecs_os_memdup(terms, term_count * ECS_SIZEOF(ecs_term_t)); + buffer_count = term_count; + } else { + terms = NULL; + } + + /* Parse expression into array of terms */ + const char *ptr = desc->expr; + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (term_count == buffer_count) { + buffer_count = buffer_count ? buffer_count * 2 : 8; + terms = ecs_os_realloc(terms, + buffer_count * ECS_SIZEOF(ecs_term_t)); + } + + terms[term_count] = term; + term_count ++; + } + + f.terms = terms; + f.term_count = term_count; + + if (!ptr) { + goto error; + } +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* If default substitution is enabled, replace DefaultSet with SuperSet */ + if (desc->substitute_default) { + for (i = 0; i < term_count; i ++) { + if (terms[i].args[0].set.mask == EcsDefaultSet) { + terms[i].args[0].set.mask = EcsSuperSet | EcsSelf; + terms[i].args[0].set.relation = EcsIsA; + } + } + } + + /* Ensure all fields are consistent and properly filled out */ + if (ecs_filter_finalize(world, &f)) { + goto error; + } + + *filter_out = f; + + /* Copy term resources. */ + if (term_count) { + if (!filter_out->expr) { + if (term_count < ECS_TERM_CACHE_SIZE) { + filter_out->terms = filter_out->term_cache; + filter_out->term_cache_used = true; + } else { + filter_out->terms = ecs_os_malloc_n(ecs_term_t, term_count); + } + } + + for (i = 0; i < term_count; i ++) { + filter_out->terms[i] = ecs_term_move(&terms[i]); + } + } else { + filter_out->terms = NULL; + } + + filter_out->name = ecs_os_strdup(desc->name); + filter_out->expr = ecs_os_strdup(desc->expr); + + ecs_assert(!filter_out->term_cache_used || + filter_out->terms == filter_out->term_cache, + ECS_INTERNAL_ERROR, NULL); + + return 0; +error: + /* NULL members that point to non-owned resources */ + if (!f.expr) { + f.terms = NULL; + } + + f.name = NULL; + f.expr = NULL; + + ecs_filter_fini(&f); + + return -1; +} + +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src) +{ + if (src) { + *dst = *src; + + int32_t term_count = src->term_count; + + if (src->term_cache_used) { + dst->terms = dst->term_cache; + } else { + dst->terms = ecs_os_memdup_n(src->terms, ecs_term_t, term_count); + } + + int i; + for (i = 0; i < term_count; i ++) { + dst->terms[i] = ecs_term_copy(&src->terms[i]); + } + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} + +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src) +{ + if (src) { + *dst = *src; + + if (src->term_cache_used) { + dst->terms = dst->term_cache; + } + + src->terms = NULL; + src->term_count = 0; + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} + +void ecs_filter_fini( + ecs_filter_t *filter) +{ + if (filter->terms) { + int i, count = filter->term_count; + for (i = 0; i < count; i ++) { + ecs_term_fini(&filter->terms[i]); + } + + if (!filter->term_cache_used) { + ecs_os_free(filter->terms); + } + } + + ecs_os_free(filter->name); + ecs_os_free(filter->expr); +} + +static +void filter_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_term_id_t *id) +{ + if (id->name) { + ecs_strbuf_appendstr(buf, id->name); + } else if (id->entity) { + char *path = ecs_get_fullpath(world, id->entity); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + } else { + ecs_strbuf_appendstr(buf, "0"); + } +} + +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, + ECS_INTERNAL_ERROR, NULL); + + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + int32_t or_count = 0; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + + if (i) { + if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { + ecs_strbuf_appendstr(&buf, " || "); + } else { + ecs_strbuf_appendstr(&buf, ", "); + } + } + + if (or_count < 1) { + if (term->inout == EcsIn) { + ecs_strbuf_appendstr(&buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendstr(&buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendstr(&buf, "[out] "); + } + } + + if (term->role && term->role != ECS_PAIR) { + ecs_strbuf_appendstr(&buf, ecs_role_str(term->role)); + ecs_strbuf_appendstr(&buf, " "); + } + + if (term->oper == EcsOr) { + or_count ++; + } else { + or_count = 0; + } + + if (term->oper == EcsNot) { + ecs_strbuf_appendstr(&buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendstr(&buf, "?"); + } + + if (term->args[0].entity == EcsThis && + ecs_term_id_is_set(&term->args[1])) + { + ecs_strbuf_appendstr(&buf, "("); + } + + if (!ecs_term_id_is_set(&term->args[1]) && + (term->pred.entity != term->args[0].entity)) + { + filter_str_add_id(world, &buf, &term->pred); + + if (!ecs_term_id_is_set(&term->args[0])) { + ecs_strbuf_appendstr(&buf, "()"); + } else if (term->args[0].entity != EcsThis) { + ecs_strbuf_appendstr(&buf, "("); + filter_str_add_id(world, &buf, &term->args[0]); + } + + if (ecs_term_id_is_set(&term->args[1])) { + ecs_strbuf_appendstr(&buf, ", "); + filter_str_add_id(world, &buf, &term->args[1]); + ecs_strbuf_appendstr(&buf, ")"); + } + } else if (!ecs_term_id_is_set(&term->args[1])) { + ecs_strbuf_appendstr(&buf, "$"); + filter_str_add_id(world, &buf, &term->pred); + } else if (ecs_term_id_is_set(&term->args[1])) { + filter_str_add_id(world, &buf, &term->pred); + ecs_strbuf_appendstr(&buf, ", "); + filter_str_add_id(world, &buf, &term->args[1]); + ecs_strbuf_appendstr(&buf, ")"); + } + } + + return ecs_strbuf_get(&buf); +} + +static +bool populate_from_column( + ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t column, + ecs_entity_t source, + ecs_id_t *id_out, + ecs_type_t *type_out, + ecs_entity_t *subject_out, + ecs_size_t *size_out, + void **ptr_out) +{ + bool has_data = false; + + if (column != -1) { + /* If source is not This, find table of source */ + if (source) { + table = ecs_get_table(world, source); + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + column = tr->column; + } + + ecs_data_t *data = flecs_table_get_data(table); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + + /* If there is no data, ensure that iterator won't try to get it */ + if (table->column_count > column) { + ecs_column_t *c = &data->columns[column]; + if (c->size) { + has_data = true; + *size_out = c->size; + } + } + + if (!has_data) { + *size_out = 0; + } + + id = ids[column]; + + if (subject_out) { + *subject_out = source; + } + + if (ptr_out) { + if (has_data) { + if (source) { + *ptr_out = (void*)ecs_get_id(world, source, id); + } else { + ecs_column_t *col = &data->columns[column]; + *ptr_out = ecs_vector_first_t( + col->data, col->size, col->alignment); + } + } else { + *ptr_out = NULL; + } + } + } + + *type_out = NULL; + *id_out = id; + + return has_data; +} + +static +void populate_from_table( + ecs_iter_t *it, + ecs_table_t *table) +{ + it->table = table; + it->count = ecs_table_count(table); + + const ecs_data_t *data = flecs_table_get_data(table); + it->data = (ecs_data_t*)data; + + if (data) { + it->table_columns = data->columns; + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + } else { + it->table_columns = NULL; + it->entities = NULL; + } +} + +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_type_t type, + ecs_id_t *ids, + int32_t *columns, + ecs_type_t *types, + ecs_entity_t *subjects, + ecs_size_t *sizes, + void **ptrs) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, + ECS_INTERNAL_ERROR, NULL); + + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + bool is_or = false; + bool or_result = false; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + ecs_type_t match_type = type; + + if (!is_or && oper == EcsOr) { + is_or = true; + or_result = false; + } else if (is_or && oper != EcsOr) { + if (!or_result) { + return false; + } + + is_or = false; + } + + ecs_entity_t subj_entity = subj->entity; + if (!subj_entity) { + continue; + } + + if (subj_entity != EcsThis) { + match_table = ecs_get_table(world, subj_entity); + if (match_table) { + match_type = match_table->type; + } else { + match_type = NULL; + } + } + + ecs_entity_t source; + + int32_t column = ecs_type_match(world, match_table, match_type, + 0, term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source); + bool result = column != -1; + + if (oper == EcsNot) { + result = !result; + } + + if (oper == EcsOptional) { + result = true; + } + + if (is_or) { + or_result |= result; + } else if (!result) { + return false; + } + + if (subj_entity != EcsThis) { + if (!source) { + source = subj_entity; + } + } + + if (columns && result) { + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(types != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(subjects != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t t_i = term->index; + + void **ptr = ptrs ? &ptrs[t_i] : NULL; + populate_from_column(world, table, term->id, column, + source, &ids[t_i], &types[t_i], &subjects[t_i], &sizes[t_i], + ptr); + + if (column != -1) { + columns[t_i] = column + 1; + } else { + columns[t_i] = 0; + } + } + } + + return !is_or || or_result; +} + +static +void term_iter_init_no_data( + ecs_term_iter_t *iter) +{ + iter->term = NULL; + iter->self_index = NULL; + iter->iter = ecs_map_iter(NULL); +} + +static +void term_iter_init_wildcard( + const ecs_world_t *world, + ecs_term_iter_t *iter) +{ + iter->term = NULL; + iter->self_index = flecs_get_id_record(world, EcsWildcard); + + if (iter->self_index) { + iter->iter = ecs_map_iter(iter->self_index->table_index); + } +} + +static +void term_iter_init( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_iter_t *iter) +{ + const ecs_term_id_t *subj = &term->args[0]; + + iter->term = term; + + if (subj->set.mask == EcsDefaultSet || subj->set.mask & EcsSelf) { + iter->self_index = flecs_get_id_record(world, term->id); + } + + if (subj->set.mask & EcsSuperSet) { + iter->set_index = flecs_get_id_record(world, + ecs_pair(subj->set.relation, EcsWildcard)); + } + + if (iter->self_index) { + iter->iter = ecs_map_iter(iter->self_index->table_index); + } else if (iter->set_index) { + iter->iter = ecs_map_iter(iter->set_index->table_index); + iter->iter_set = true; + } +} + +ecs_iter_t ecs_term_iter( + const ecs_world_t *stage, + ecs_term_t *term) +{ + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term->id != 0, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + if (ecs_term_finalize(world, NULL, NULL, term)) { + /* Invalid term */ + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage, + .column_count = 1 + }; + + term_iter_init(world, term, &it.iter.term); + + return it; +} + +static +ecs_table_record_t* term_iter_next( + ecs_world_t *world, + ecs_term_iter_t *iter, + ecs_entity_t *source_out) +{ + ecs_table_t *table = NULL; + ecs_entity_t source = 0; + ecs_table_record_t *tr; + + do { + tr = ecs_map_next(&iter->iter, ecs_table_record_t, NULL); + if (!tr) { + if (!iter->iter_set) { + if (iter->set_index) { + iter->iter = ecs_map_iter(iter->set_index->table_index); + tr = ecs_map_next(&iter->iter, ecs_table_record_t, NULL); + iter->iter_set = true; + } + } + + if (!tr) { + return NULL; + } + } + + table = tr->table; + + if (!ecs_table_count(table)) { + continue; + } + + if (iter->iter_set) { + const ecs_term_t *term = iter->term; + const ecs_term_id_t *subj = &term->args[0]; + + if (iter->self_index) { + if (ecs_map_has(iter->self_index->table_index, table->id)) { + /* If the table has the id itself and this term matched Self + * we already matched it */ + continue; + } + } + + /* Test if following the relation finds the id */ + int32_t index = ecs_type_match(world, table, table->type, 0, + term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source); + if (index == -1) { + continue; + } + + ecs_assert(source != 0, ECS_INTERNAL_ERROR, NULL); + } + + break; + } while (true); + + if (source_out) { + *source_out = source; + } + + return tr; +} + +bool ecs_term_next( + ecs_iter_t *it) +{ + ecs_term_iter_t *iter = &it->iter.term; + ecs_term_t *term = iter->term; + ecs_world_t *world = it->real_world; + + ecs_entity_t source; + ecs_table_record_t *tr = term_iter_next(world, iter, &source); + if (!tr) { + it->is_valid = false; + return false; + } + + ecs_table_t *table = tr->table; + + /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ + ecs_assert(source || !iter->iter_set, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = flecs_table_get_data(table); + + it->table = table; + it->data = data; + it->ids = &iter->id; + it->types = &iter->type; + it->columns = &iter->column; + it->subjects = &iter->subject; + it->sizes = &iter->size; + it->ptrs = &iter->ptr; + + it->table_columns = data->columns; + it->count = ecs_table_count(table); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->is_valid = true; + + bool has_data = populate_from_column(world, table, term->id, tr->column, + source, &iter->id, &iter->type, &iter->subject, &iter->size, + &iter->ptr); + + if (!source) { + if (has_data) { + iter->column = tr->column + 1; + } else { + iter->column = 0; + } + } else { + iter->column = -1; /* Point to ref */ + } + + return true; +} + +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) +{ + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage + }; + + ecs_filter_iter_t *iter = &it.iter.filter; + if (filter) { + iter->filter = *filter; + + if (filter->term_cache_used) { + iter->filter.terms = iter->filter.term_cache; + } + + ecs_filter_finalize(world, &iter->filter); + + ecs_assert(!filter->term_cache_used || + filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_filter_init(world, &iter->filter, &(ecs_filter_desc_t) { + .terms = {{ .id = EcsWildcard }} + }); + + filter = &iter->filter; + } + + int32_t i, term_count = filter->term_count; + ecs_term_t *terms = filter->terms; + int32_t min_count = -1; + int32_t min_term_index = -1; + + /* Find term that represents smallest superset */ + if (filter->match_this) { + iter->kind = EcsFilterIterEvalIndex; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + ecs_assert(term != NULL, ECS_INTERNAL_ERROR, NULL); + + if (term->oper != EcsAnd) { + continue; + } + + if (term->args[0].entity != EcsThis) { + continue; + } + + ecs_id_record_t *idr = flecs_get_id_record(world, term->id); + if (!idr) { + /* If one of the terms does not match with any data, iterator + * should not return anything */ + term_iter_init_no_data(&iter->term_iter); + return it; + } + + int32_t table_count = ecs_map_count(idr->table_index); + if (min_count == -1 || table_count < min_count) { + min_count = table_count; + min_term_index = i; + } + } + + iter->min_term_index = min_term_index; + + if (min_term_index == -1) { + term_iter_init_wildcard(world, &iter->term_iter); + } else { + term_iter_init(world, &terms[min_term_index], &iter->term_iter); + } + } else { + /* If filter has no this terms, no tables need to be evaluated */ + iter->kind = EcsFilterIterEvalNone; + } + + it.column_count = filter->term_count_actual; + + if (filter->terms == filter->term_cache) { + /* Because we're returning the iterator by value, the address of the + * term cache changes. The ecs_filter_next function will set the correct + * address when it detects that terms is set to NULL */ + iter->filter.terms = NULL; + } + + return it; +} + +bool ecs_filter_next( + ecs_iter_t *it) +{ + ecs_filter_iter_t *iter = &it->iter.filter; + ecs_filter_t *filter = &iter->filter; + ecs_world_t *world = it->real_world; + + if (!filter->terms) { + filter->terms = filter->term_cache; + } + + ecs_iter_init(it); + + if (iter->kind == EcsFilterIterEvalIndex) { + ecs_term_iter_t *term_iter = &iter->term_iter; + ecs_table_t *table; + bool match; + + do { + ecs_entity_t source; + ecs_table_record_t *tr = term_iter_next(world, term_iter, &source); + if (!tr) { + goto done; + } + + table = tr->table; + match = flecs_filter_match_table(world, filter, table, table->type, + it->ids, it->columns, it->types, it->subjects, it->sizes, + it->ptrs); + } while (!match); + + populate_from_table(it, table); + + goto yield; + } + +done: + ecs_iter_fini(it); + return false; + +yield: + it->is_valid = true; + return true; +} + +static +void observer_callback(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_world_t *world = it->world; + + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = it->table; + ecs_type_t type = table->type; + + ecs_iter_t user_it = *it; + user_it.column_count = o->filter.term_count_actual, + user_it.ids = NULL; + user_it.columns = NULL; + user_it.types = NULL; + user_it.subjects = NULL; + user_it.sizes = NULL; + user_it.ptrs = NULL; + + ecs_iter_init(&user_it); + + if (flecs_filter_match_table(world, &o->filter, table, type, + user_it.ids, user_it.columns, user_it.types, user_it.subjects, + user_it.sizes, user_it.ptrs)) + { + ecs_data_t *data = flecs_table_get_data(table); + + user_it.ids[it->term_index] = it->event_id; + + user_it.system = o->entity; + user_it.term_index = it->term_index; + user_it.self = o->self; + user_it.ctx = o->ctx; + user_it.column_count = o->filter.term_count_actual, + user_it.table_columns = data->columns, + o->action(&user_it); + } + + ecs_iter_fini(&user_it); +} + +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + /* If entity is provided, create it */ + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t entity = ecs_entity_init(world, &desc->entity); + + bool added = false; + EcsObserver *comp = ecs_get_mut(world, entity, EcsObserver, &added); + if (added) { + ecs_observer_t *observer = flecs_sparse_add( + world->observers, ecs_observer_t); + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + observer->id = flecs_sparse_last_id(world->observers); + + /* Make writeable copy of filter desc so that we can set name. This will + * make debugging easier, as any error messages related to creating the + * filter will have the name of the observer. */ + ecs_filter_desc_t filter_desc = desc->filter; + filter_desc.name = desc->entity.name; + + /* Parse filter */ + if (ecs_filter_init(world, &observer->filter, &filter_desc)) { + flecs_observer_fini(world, observer); + return 0; + } + + ecs_filter_t *filter = &observer->filter; + + /* Create a trigger for each term in the filter */ + observer->triggers = ecs_os_malloc(ECS_SIZEOF(ecs_entity_t) * + observer->filter.term_count); + + int i; + for (i = 0; i < filter->term_count; i ++) { + const ecs_term_t *terms = filter->terms; + const ecs_term_t *t = &terms[i]; + + if (t->oper == EcsNot || terms[i].args[0].entity != EcsThis) { + /* No need to trigger on components that the entity should not + * have, or on components that are not defined on the entity */ + observer->triggers[i] = 0; + continue; + } + + ecs_trigger_desc_t trigger_desc = { + .term = *t, + .callback = observer_callback, + .ctx = observer, + .binding_ctx = desc->binding_ctx + }; + + ecs_os_memcpy(trigger_desc.events, desc->events, + ECS_SIZEOF(ecs_entity_t) * ECS_TRIGGER_DESC_EVENT_COUNT_MAX); + observer->triggers[i] = ecs_trigger_init(world, &trigger_desc); + } + + observer->action = desc->callback; + observer->self = desc->self; + observer->ctx = desc->ctx; + observer->binding_ctx = desc->binding_ctx; + observer->ctx_free = desc->ctx_free; + observer->binding_ctx_free = desc->binding_ctx_free; + observer->event_count = 0; + ecs_os_memcpy(observer->events, desc->events, + observer->event_count * ECS_SIZEOF(ecs_entity_t)); + observer->entity = entity; + + comp->observer = observer; + } else { + ecs_assert(comp->observer != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If existing entity handle was provided, override existing params */ + if (existing) { + if (desc->callback) { + ((ecs_observer_t*)comp->observer)->action = desc->callback; + } + if (desc->ctx) { + ((ecs_observer_t*)comp->observer)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ((ecs_observer_t*)comp->observer)->binding_ctx = + desc->binding_ctx; + } + } + } + + return entity; +} + +void flecs_observer_fini( + ecs_world_t *world, + ecs_observer_t *observer) +{ + int i, count = observer->filter.term_count; + for (i = 0; i < count; i ++) { + ecs_entity_t trigger = observer->triggers[i]; + if (trigger) { + ecs_delete(world, trigger); + } + } + ecs_os_free(observer->triggers); + + ecs_filter_fini(&observer->filter); + + if (observer->ctx_free) { + observer->ctx_free(observer->ctx); + } + + if (observer->binding_ctx_free) { + observer->binding_ctx_free(observer->binding_ctx); + } + + flecs_sparse_remove(world->observers, observer->id); +} + +void* ecs_get_observer_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsObserver *o = ecs_get(world, observer, EcsObserver); + if (o) { + return o->observer->ctx; + } else { + return NULL; + } +} + +void* ecs_get_observer_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsObserver *o = ecs_get(world, observer, EcsObserver); + if (o) { + return o->observer->binding_ctx; + } else { + return NULL; + } +} + + +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_deinit( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); +} + +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_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t hi = elem >> 6; + int32_t lo = elem & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +} + +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +} + +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_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = flecs_bitset_get(bs, last); + flecs_bitset_set(bs, elem, last_value); + bs->count --; +} + +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_assert(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(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); +} + +/* Add an extra element to the buffer */ +static +void ecs_strbuf_grow( + ecs_strbuf_t *b) +{ + /* Allocate new element */ + ecs_strbuf_element_embedded *e = ecs_os_malloc(sizeof(ecs_strbuf_element_embedded)); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = true; + e->super.buf = e->buf; + e->super.pos = 0; + e->super.next = NULL; +} + +/* Add an extra dynamic element */ +static +void ecs_strbuf_grow_str( + ecs_strbuf_t *b, + char *str, + char *alloc_str, + int32_t size) +{ + /* Allocate new element */ + ecs_strbuf_element_str *e = ecs_os_malloc(sizeof(ecs_strbuf_element_str)); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = false; + e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); + e->super.next = NULL; + e->super.buf = str; + e->alloc_str = alloc_str; +} + +static +char* ecs_strbuf_ptr( + ecs_strbuf_t *b) +{ + if (b->buf) { + return &b->buf[b->current->pos]; + } else { + return &b->current->buf[b->current->pos]; + } +} + +/* Compute the amount of space left in the current element */ +static +int32_t ecs_strbuf_memLeftInCurrentElement( + ecs_strbuf_t *b) +{ + if (b->current->buffer_embedded) { + return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; + } else { + return 0; + } +} + +/* Compute the amount of space left */ +static +int32_t ecs_strbuf_memLeft( + ecs_strbuf_t *b) +{ + if (b->max) { + return b->max - b->size - b->current->pos; + } else { + return INT_MAX; + } +} + +static +void ecs_strbuf_init( + ecs_strbuf_t *b) +{ + /* Initialize buffer structure only once */ + if (!b->elementCount) { + b->size = 0; + b->firstElement.super.next = NULL; + b->firstElement.super.pos = 0; + b->firstElement.super.buffer_embedded = true; + b->firstElement.super.buf = b->firstElement.buf; + b->elementCount ++; + b->current = (ecs_strbuf_element*)&b->firstElement; + } +} + +/* Quick custom function to copy a maxium number of characters and + * simultaneously determine length of source string. */ +static +int32_t fast_strncpy( + char * dst, + const char * src, + int n_cpy, + int n) +{ + ecs_assert(n_cpy >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(n >= 0, ECS_INTERNAL_ERROR, NULL); + + const char *ptr, *orig = src; + char ch; + + for (ptr = src; (ptr - orig < n) && (ch = *ptr); ptr ++) { + if (ptr - orig < n_cpy) { + *dst = ch; + dst ++; + } + } + + ecs_assert(ptr - orig < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + + return (int32_t)(ptr - orig); +} + +/* Append a format string to a buffer */ +static +bool ecs_strbuf_vappend_intern( + ecs_strbuf_t *b, + const char* str, + va_list args) +{ + bool result = true; + va_list arg_cpy; + + if (!str) { + return result; + } + + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + + if (!memLeft) { + return false; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; + + va_copy(arg_cpy, args); + memRequired = vsnprintf( + ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + + ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); + + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* If string is a format string, a new buffer of size memRequired is + * needed to re-evaluate the format string and only use the part that + * wasn't already copied to the previous element */ + if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { + /* Resulting string fits in standard-size buffer. Note that the + * entire string needs to fit, not just the remainder, as the + * format string cannot be partially evaluated */ + ecs_strbuf_grow(b); + + /* Copy entire string to new buffer */ + ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); + + /* Ignore the part of the string that was copied into the + * previous buffer. The string copied into the new buffer could + * be memmoved so that only the remainder is left, but that is + * most likely more expensive than just keeping the entire + * string. */ + + /* Update position in buffer */ + b->current->pos += memRequired; + } else { + /* Resulting string does not fit in standard-size buffer. + * Allocate a new buffer that can hold the entire string. */ + char *dst = ecs_os_malloc(memRequired + 1); + ecs_os_vsprintf(dst, str, arg_cpy); + ecs_strbuf_grow_str(b, dst, dst, memRequired); + } + } else { + /* Buffer max has been reached */ + result = false; + } + + va_end(arg_cpy); + + return result; +} + +static +bool ecs_strbuf_append_intern( + ecs_strbuf_t *b, + const char* str, + int n) +{ + bool result = true; + + if (!str) { + return result; + } + + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + + if (memLeft <= 0) { + return false; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; + + if (n < 0) n = INT_MAX; + + memRequired = fast_strncpy(ecs_strbuf_ptr(b), str, max_copy, n); + + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* Element was not large enough, but buffer still has space */ + b->current->pos += memLeftInElement; + memRequired -= memLeftInElement; + + /* Current element was too small, copy remainder into new element */ + if (memRequired < ECS_STRBUF_ELEMENT_SIZE) { + /* A standard-size buffer is large enough for the new string */ + ecs_strbuf_grow(b); + + /* Copy the remainder to the new buffer */ + if (n) { + /* If a max number of characters to write is set, only a + * subset of the string should be copied to the buffer */ + ecs_os_strncpy( + ecs_strbuf_ptr(b), + str + memLeftInElement, + (size_t)memRequired); + } else { + ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement); + } + + /* Update to number of characters copied to new buffer */ + b->current->pos += memRequired; + } else { + char *remainder = ecs_os_strdup(str + memLeftInElement); + ecs_strbuf_grow_str(b, remainder, remainder, memRequired); + } + } else { + /* Buffer max has been reached */ + result = false; + } + + return result; +} + +bool ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) +{ + bool result = ecs_strbuf_vappend_intern( + b, fmt, args + ); + + return result; +} + +bool ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) +{ + va_list args; + va_start(args, fmt); + bool result = ecs_strbuf_vappend_intern( + b, fmt, args + ); + va_end(args); + + return result; +} + +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) +{ + return ecs_strbuf_append_intern( + b, str, len + ); +} + +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *b, + char* str) +{ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, str, str, 0); + return true; +} + +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *b, + const char* str) +{ + /* Removes const modifier, but logic prevents changing / delete string */ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, (char*)str, NULL, 0); + return true; +} + +bool ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) +{ + return ecs_strbuf_append_intern( + b, str, -1 + ); +} + +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer) +{ + if (src_buffer->elementCount) { + if (src_buffer->buf) { + return ecs_strbuf_appendstr(dst_buffer, src_buffer->buf); + } else { + ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; + + /* Copy first element as it is inlined in the src buffer */ + ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + + while ((e = e->next)) { + dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); + *dst_buffer->current->next = *e; + } + } + + *src_buffer = ECS_STRBUF_INIT; + } + + return true; +} + +char* ecs_strbuf_get(ecs_strbuf_t *b) { + char* result = NULL; + + if (b->elementCount) { + if (b->buf) { + b->buf[b->current->pos] = '\0'; + result = ecs_os_strdup(b->buf); + } else { + void *next = NULL; + int32_t len = b->size + b->current->pos + 1; + + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + + result = ecs_os_malloc(len); + char* ptr = result; + + do { + ecs_os_memcpy(ptr, e->buf, e->pos); + ptr += e->pos; + next = e->next; + if (e != &b->firstElement.super) { + if (!e->buffer_embedded) { + ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); + } + ecs_os_free(e); + } + } while ((e = next)); + + result[len - 1] = '\0'; + } + } else { + result = NULL; + } + + b->elementCount = 0; + + return result; +} + +void ecs_strbuf_reset(ecs_strbuf_t *b) { + if (b->elementCount && !b->buf) { + void *next = NULL; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + do { + next = e->next; + if (e != (ecs_strbuf_element*)&b->firstElement) { + ecs_os_free(e); + } + } while ((e = next)); + } + + *b = ECS_STRBUF_INIT; +} + +void ecs_strbuf_list_push( + ecs_strbuf_t *buffer, + const char *list_open, + const char *separator) +{ + buffer->list_sp ++; + buffer->list_stack[buffer->list_sp].count = 0; + buffer->list_stack[buffer->list_sp].separator = separator; + + if (list_open) { + ecs_strbuf_appendstr(buffer, list_open); + } +} + +void ecs_strbuf_list_pop( + ecs_strbuf_t *buffer, + const char *list_close) +{ + buffer->list_sp --; + + if (list_close) { + ecs_strbuf_appendstr(buffer, list_close); + } +} + +void ecs_strbuf_list_next( + ecs_strbuf_t *buffer) +{ + int32_t list_sp = buffer->list_sp; + if (buffer->list_stack[list_sp].count != 0) { + ecs_strbuf_appendstr(buffer, buffer->list_stack[list_sp].separator); + } + buffer->list_stack[list_sp].count ++; +} + +bool ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...) +{ + ecs_strbuf_list_next(buffer); + + va_list args; + va_start(args, fmt); + bool result = ecs_strbuf_vappend_intern( + buffer, fmt, args + ); + va_end(args); + + return result; +} + +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str) +{ + ecs_strbuf_list_next(buffer); + return ecs_strbuf_appendstr(buffer, str); +} + +/* -- Private functions -- */ + +/* O(n) algorithm to check whether type 1 is equal or superset of type 2 */ +ecs_entity_t flecs_type_contains( + const ecs_world_t *world, + ecs_type_t type_1, + ecs_type_t type_2, + bool match_all, + bool match_prefab) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + if (!type_1) { + return 0; + } + + ecs_assert(type_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + if (type_1 == type_2) { + return *(ecs_vector_first(type_1, ecs_entity_t)); + } + + if (ecs_vector_count(type_2) == 1) { + return ecs_type_has_id(world, type_1, + ecs_vector_first(type_2, ecs_id_t)[0], !match_prefab); + } + + int32_t i_2, i_1 = 0; + ecs_entity_t e1 = 0; + ecs_entity_t *t1_array = ecs_vector_first(type_1, ecs_entity_t); + ecs_entity_t *t2_array = ecs_vector_first(type_2, ecs_entity_t); + + ecs_assert(t1_array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t2_array != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t t1_count = ecs_vector_count(type_1); + int32_t t2_count = ecs_vector_count(type_2); + + for (i_2 = 0; i_2 < t2_count; i_2 ++) { + ecs_entity_t e2 = t2_array[i_2]; + + if (i_1 >= t1_count) { + return 0; + } + + e1 = t1_array[i_1]; + + if (e2 > e1) { + do { + i_1 ++; + if (i_1 >= t1_count) { + return 0; + } + e1 = t1_array[i_1]; + } while (e2 > e1); + } + + if (e1 != e2) { + if (e1 != e2) { + if (match_all) return 0; + } else if (!match_all) { + return e1; + } + } else { + if (!match_all) return e1; + i_1 ++; + if (i_1 < t1_count) { + e1 = t1_array[i_1]; + } + } + } + + if (match_all) { + return e1; + } else { + return 0; + } +} + +/* -- Public API -- */ + +ecs_type_t ecs_type_merge( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t to_add, + ecs_type_t to_remove) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This function is allowed while staged, as long as the type already + * exists. If the type does not exist yet and traversing the table graph + * results in the creation of a table, an assert will trigger. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_table_t *table = ecs_table_from_type(unsafe_world, type); + ecs_ids_t add_array = flecs_type_to_ids(to_add); + ecs_ids_t remove_array = flecs_type_to_ids(to_remove); + + table = flecs_table_traverse_remove( + unsafe_world, table, &remove_array, NULL); + + table = flecs_table_traverse_add( + unsafe_world, table, &add_array, NULL); + + if (!table) { + return NULL; + } else { + return table->type; + } +} + +static +bool has_case( + const ecs_world_t *world, + ecs_entity_t sw_case, + ecs_entity_t e) +{ + const EcsType *type_ptr = ecs_get(world, e & ECS_COMPONENT_MASK, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_type_has_id(world, type_ptr->normalized, sw_case, false); +} + +static +bool match_id( + const ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t match_with) +{ + if (ECS_HAS_ROLE(match_with, CASE)) { + ecs_entity_t sw_case = match_with & ECS_COMPONENT_MASK; + if (ECS_HAS_ROLE(id, SWITCH) && has_case(world, sw_case, id)) { + return 1; + } else { + return 0; + } + } else { + return ecs_id_match(id, match_with); + } +} + +static +int32_t search_type( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + int32_t depth, + ecs_entity_t *out) +{ + if (!id) { + return -1; + } + + if (!type) { + return -1; + } + + if (max_depth && depth > max_depth) { + return -1; + } + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + + if (depth >= min_depth) { + if (table && !offset && !(ECS_HAS_ROLE(id, CASE))) { + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + if (tr) { + return tr->column; + } + } else { + for (i = offset; i < count; i ++) { + if (match_id(world, ids[i], id)) { + return i; + } + } + } + } + + if (rel && id != EcsPrefab && id != EcsDisabled && + id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) + { + for (i = 0; i < count; i ++) { + ecs_entity_t e = ids[i]; + if (!ECS_HAS_RELATION(e, rel)) { + continue; + } + + ecs_entity_t obj = ecs_pair_object(world, e); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *obj_table = ecs_get_table(world, obj); + if (!obj_table) { + continue; + } + + if ((search_type(world, obj_table, obj_table->type, 0, id, + rel, min_depth, max_depth, depth + 1, out) != -1)) + { + if (out && !*out) { + *out = obj; + } + return i; + + /* If the id could not be found on the object and the relationship + * is not IsA, try substituting the object type with IsA */ + } else if (rel != EcsIsA) { + if (search_type(world, obj_table, obj_table->type, 0, + id, EcsIsA, 1, 0, 0, out) != -1) + { + if (out && !*out) { + *out = obj; + } + return i; + } + } + } + } + + return -1; +} + +bool ecs_type_has_id( + const ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + bool owned) +{ + return search_type(world, NULL, type, 0, id, owned ? 0 : EcsIsA, 0, 0, 0, NULL) != -1; +} + +int32_t ecs_type_index_of( + ecs_type_t type, + int32_t offset, + ecs_id_t id) +{ + return search_type(NULL, NULL, type, offset, id, 0, 0, 0, 0, NULL); +} + +int32_t ecs_type_match( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + ecs_entity_t *out) +{ + if (out) { + *out = 0; + } + return search_type(world, table, type, offset, id, rel, min_depth, max_depth, 0, out); +} + +bool ecs_type_has_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has) +{ + return flecs_type_contains(world, type, has, true, false) != 0; +} + +bool ecs_type_owns_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has, + bool owned) +{ + return flecs_type_contains(world, type, has, true, !owned) != 0; +} + +ecs_type_t ecs_type_add( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This function is allowed while staged, as long as the type already + * exists. If the type does not exist yet and traversing the table graph + * results in the creation of a table, an assert will trigger. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_table_t *table = ecs_table_from_type(unsafe_world, type); + + ecs_ids_t entities = { + .array = &e, + .count = 1 + }; + + table = flecs_table_traverse_add(unsafe_world, table, &entities, NULL); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +ecs_type_t ecs_type_remove( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This function is allowed while staged, as long as the type already + * exists. If the type does not exist yet and traversing the table graph + * results in the creation of a table, an assert will trigger. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_table_t *table = ecs_table_from_type(unsafe_world, type); + + ecs_ids_t entities = { + .array = &e, + .count = 1 + }; + + table = flecs_table_traverse_remove(unsafe_world, table, &entities, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +char* ecs_type_str( + const ecs_world_t *world, + ecs_type_t type) +{ + if (!type) { + return ecs_os_strdup(""); + } + + ecs_vector_t *chbuf = ecs_vector_new(char, 32); + char *dst; + + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + char buffer[256]; + ecs_size_t len; + + if (i) { + *(char*)ecs_vector_add(&chbuf, char) = ','; + } + + if (e == 1) { + ecs_os_strcpy(buffer, "EcsComponent"); + len = ecs_os_strlen("EcsComponent"); + } else { + len = flecs_from_size_t(ecs_id_str(world, e, buffer, 256)); + } + + dst = ecs_vector_addn(&chbuf, char, len); + ecs_os_memcpy(dst, buffer, len); + } + + *(char*)ecs_vector_add(&chbuf, char) = '\0'; + + char* result = ecs_os_strdup(ecs_vector_first(chbuf, char)); + ecs_vector_free(chbuf); + return result; +} + +ecs_entity_t ecs_type_get_entity_for_xor( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t xor) +{ + ecs_assert( + ecs_type_has_id(world, type, ECS_XOR | xor, true), + ECS_INVALID_PARAMETER, NULL); + + const EcsType *type_ptr = ecs_get(world, xor, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t xor_type = type_ptr->normalized; + ecs_assert(xor_type != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + for (i = 0; i < count; i ++) { + if (ecs_type_has_id(world, xor_type, array[i], true)) { + return array[i]; + } + } + + return 0; +} + +void ecs_os_api_impl(ecs_os_api_t *api); + +static bool ecs_os_api_initialized = false; +static int ecs_os_api_init_count = 0; + +ecs_os_api_t ecs_os_api; + +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; + } +} + +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_(); + } + } +} + +static +void ecs_log(const char *fmt, va_list args) { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); +} + +static +void ecs_log_error(const char *fmt, va_list args) { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +static +void ecs_log_debug(const char *fmt, va_list args) { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); +} + +static +void ecs_log_warning(const char *fmt, va_list args) { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +void ecs_os_dbg(const char *fmt, ...) { +#ifndef NDEBUG + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_debug_) { + ecs_os_api.log_debug_(fmt, args); + } + va_end(args); +#else + (void)fmt; +#endif +} + +void ecs_os_warn(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_warning_) { + ecs_os_api.log_warning_(fmt, args); + } + va_end(args); +} + +void ecs_os_log(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_) { + ecs_os_api.log_(fmt, args); + } + va_end(args); +} + +void ecs_os_err(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_error_) { + ecs_os_api.log_error_(fmt, args); + } + va_end(args); +} + +static +void ecs_os_gettime(ecs_time_t *time) +{ + uint64_t now = flecs_os_time_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_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_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_api_realloc_count ++; + } else { + /* If not actually reallocing, treat as malloc */ + ecs_os_api_malloc_count ++; + } + + return realloc(ptr, (size_t)size); +} + +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + 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; + } +} + +/* 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_OS_LINUX) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".so"); +#elif defined(ECS_OS_DARWIN) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dylib"); +#elif defined(ECS_OS_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&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_appendstr(&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; + } + + flecs_os_time_setup(); + + /* 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.sleep_ = flecs_os_time_sleep; + ecs_os_api.get_time_ = ecs_os_gettime; + + /* Logging */ + ecs_os_api.log_ = ecs_log; + ecs_os_api.log_error_ = ecs_log_error; + ecs_os_api.log_debug_ = ecs_log_debug; + ecs_os_api.log_warning_ = ecs_log_warning; + + /* 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; +} + +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); +} + +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL); +} + +bool ecs_os_has_logging(void) { + return + (ecs_os_api.log_ != NULL) && + (ecs_os_api.log_error_ != NULL) && + (ecs_os_api.log_debug_ != NULL) && + (ecs_os_api.log_warning_ != 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(_MSC_VER) +static char error_str[255]; +#endif + +const char* ecs_os_strerror(int err) { +#if defined(_MSC_VER) + strerror_s(error_str, 255, err); + return error_str; +#else + return strerror(err); +#endif +} + +#ifdef FLECS_SYSTEMS_H +#endif + +static +void activate_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table, + bool active); + +/* Builtin group_by callback for Cascade terms. + * This function traces the hierarchy depth of an entity type by following a + * relation upwards (to its 'parents') for as long as those parents have the + * specified component id. + * The result of the function is the number of parents with the provided + * component for a given relation. */ +static +int32_t group_by_cascade( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component, + void *ctx) +{ + int32_t result = 0; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + ecs_term_t *term = ctx; + ecs_entity_t relation = term->args[0].set.relation; + + /* Cascade needs a relation to calculate depth from */ + ecs_assert(relation != 0, ECS_INVALID_PARAMETER, NULL); + + /* Should only be used with cascade terms */ + ecs_assert(term->args[0].set.mask & EcsCascade, + ECS_INVALID_PARAMETER, NULL); + + /* Iterate back to front as relations are more likely to occur near the + * end of a type. */ + for (i = count - 1; i >= 0; i --) { + /* Find relation & relation object in entity type */ + if (ECS_HAS_RELATION(array[i], relation)) { + ecs_type_t obj_type = ecs_get_type(world, + ecs_pair_object(world, array[i])); + int32_t j, c_count = ecs_vector_count(obj_type); + ecs_entity_t *c_array = ecs_vector_first(obj_type, ecs_entity_t); + + /* Iterate object type, check if it has the specified component */ + for (j = 0; j < c_count; j ++) { + /* If it has the component, it is part of the tree matched by + * the query, increase depth */ + if (c_array[j] == component) { + result ++; + + /* Recurse to test if the object has matching parents */ + result += group_by_cascade(world, obj_type, component, ctx); + break; + } + } + + if (j != c_count) { + break; + } + + /* If the id doesn't have a role set, we'll find no more relations */ + } else if (!(array[i] & ECS_ROLE_MASK)) { + break; + } + } + + return result; +} + +static +int table_compare( + const void *t1, + const void *t2) +{ + const ecs_matched_table_t *table_1 = t1; + const ecs_matched_table_t *table_2 = t2; + + return table_1->rank - table_2->rank; +} + +static +bool has_auto_activation( + ecs_query_t *q) +{ + /* Only a basic query with no additional features does table activation */ + return !(q->flags & EcsQueryNoActivation); +} + +static +void order_grouped_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + if (query->group_by) { + ecs_vector_sort(query->tables, ecs_matched_table_t, table_compare); + + /* Recompute the table indices by first resetting all indices, and then + * re-adding them one by one. */ + if (has_auto_activation(query)) { + ecs_map_iter_t it = ecs_map_iter(query->table_indices); + ecs_table_indices_t *ti; + while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) { + /* If table is registered, it must have at least one index */ + int32_t count = ti->count; + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + (void)count; + + /* Only active tables are reordered, so don't reset inactive + * tables */ + if (ti->indices[0] >= 0) { + ti->count = 0; + } + } + } + + /* Re-register monitors after tables have been reordered. This will update + * the table administration with the new matched_table ids, so that when a + * monitor is executed we can quickly find the right matched_table. */ + if (query->flags & EcsQueryMonitor) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + flecs_table_notify(world, table->table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query, + .matched_table_index = table_i + }); + }); + } + + /* Update table index */ + if (has_auto_activation(query)) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + ecs_table_indices_t *ti = ecs_map_get(query->table_indices, + ecs_table_indices_t, table->table->id); + + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ti->indices[ti->count] = table_i; + ti->count ++; + }); + } + } + + query->match_count ++; + query->needs_reorder = false; +} + +static +void group_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_matched_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (query->group_by) { + ecs_assert(table->table != NULL, ECS_INTERNAL_ERROR, NULL); + table->rank = query->group_by(world, table->table->type, + query->group_by_id, query->group_by_ctx); + } else { + table->rank = 0; + } +} + +/* Rank all tables of query. Only necessary if a new ranking function was + * provided or if a monitored entity set the component used for ranking. */ +static +void group_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + if (query->group_by) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + group_table(world, query, table); + }); + + ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, { + group_table(world, query, table); + }); + } +} + +#ifndef NDEBUG + +static +const char* query_name( + ecs_world_t *world, + ecs_query_t *q) +{ + if (q->system) { + return ecs_get_name(world, q->system); + } else if (q->filter.name) { + return q->filter.name; + } else { + return q->filter.expr; + } +} + +#endif + +static +int get_comp_and_src( + ecs_world_t *world, + ecs_query_t *query, + int32_t t, + ecs_table_t *table_arg, + ecs_entity_t *component_out, + ecs_entity_t *entity_out) +{ + ecs_entity_t component = 0, entity = 0; + + ecs_term_t *terms = query->filter.terms; + int32_t term_count = query->filter.term_count; + ecs_term_t *term = &terms[t]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t op = term->oper; + + if (op == EcsNot) { + entity = subj->entity; + } + + if (!subj->entity) { + component = term->id; + } else { + ecs_table_t *table = table_arg; + if (subj->entity != EcsThis) { + table = ecs_get_table(world, subj->entity); + } + + ecs_type_t type = NULL; + if (table) { + type = table->type; + } + + if (op == EcsOr) { + for (; t < term_count; t ++) { + term = &terms[t]; + + /* Keep iterating until the next non-OR expression */ + if (term->oper != EcsOr) { + t --; + break; + } + + if (!component) { + ecs_entity_t source = 0; + int32_t result = ecs_type_match(world, table, type, + 0, term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, + &source); + + if (result != -1) { + component = term->id; + } + + if (source) { + entity = source; + } + } + } + } else { + component = term->id; + + ecs_entity_t source = 0; + bool result = ecs_type_match(world, table, type, 0, component, + subj->set.relation, subj->set.min_depth, subj->set.max_depth, + &source) != -1; + + if (op == EcsNot) { + result = !result; + } + + /* Optional terms may not have the component. *From terms contain + * the id of a type of which the contents must match, but the type + * itself does not need to match. */ + if (op == EcsOptional || op == EcsAndFrom || op == EcsOrFrom || + op == EcsNotFrom) + { + result = true; + } + + /* Table has already been matched, so unless column is optional + * any components matched from the table must be available. */ + if (table == table_arg) { + ecs_assert(result == true, ECS_INTERNAL_ERROR, NULL); + } + + if (source) { + entity = source; + } + } + + if (subj->entity != EcsThis) { + entity = subj->entity; + } + } + + if (entity == EcsThis) { + entity = 0; + } + + *component_out = component; + *entity_out = entity; + + return t; +} + +typedef struct pair_offset_t { + int32_t index; + int32_t count; +} pair_offset_t; + +/* Get index for specified pair. Take into account that a pair can be matched + * multiple times per table, by keeping an offset of the last found index */ +static +int32_t get_pair_index( + ecs_type_t table_type, + ecs_id_t pair, + int32_t column_index, + pair_offset_t *pair_offsets, + int32_t count) +{ + int32_t result; + + /* The count variable keeps track of the number of times a pair has been + * matched with the current table. Compare the count to check if the index + * was already resolved for this iteration */ + if (pair_offsets[column_index].count == count) { + /* If it was resolved, return the last stored index. Subtract one as the + * index is offset by one, to ensure we're not getting stuck on the same + * index. */ + result = pair_offsets[column_index].index - 1; + } else { + /* First time for this iteration that the pair index is resolved, look + * it up in the type. */ + result = ecs_type_index_of(table_type, + pair_offsets[column_index].index, pair); + pair_offsets[column_index].index = result + 1; + pair_offsets[column_index].count = count; + } + + return result; +} + +static +int32_t get_component_index( + ecs_world_t *world, + ecs_table_t *table, + ecs_type_t table_type, + ecs_entity_t *component_out, + int32_t column_index, + ecs_oper_kind_t op, + pair_offset_t *pair_offsets, + int32_t count) +{ + int32_t result = 0; + ecs_entity_t component = *component_out; + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (component) { + /* If requested component is a case, find the corresponding switch to + * lookup in the table */ + if (ECS_HAS_ROLE(component, CASE)) { + result = flecs_table_switch_from_case( + world, table, component); + ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL); + + result += table->sw_column_offset; + } else + if (ECS_HAS_ROLE(component, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(component); + ecs_entity_t obj = ECS_PAIR_OBJECT(component); + + /* Both the relationship and the object of the pair must be set */ + ecs_assert(rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(obj != 0, ECS_INVALID_PARAMETER, NULL); + + if (rel == EcsWildcard || obj == EcsWildcard) { + ecs_assert(pair_offsets != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get index of pair. Start looking from the last pair index + * as this may not be the first instance of the pair. */ + result = get_pair_index( + table_type, component, column_index, pair_offsets, count); + + if (result != -1) { + /* If component of current column is a pair, get the actual + * pair type for the table, so the system can see which + * component the pair was applied to */ + ecs_entity_t *pair = ecs_vector_get( + table_type, ecs_entity_t, result); + *component_out = *pair; + + char buf[256]; ecs_id_str(world, *pair, buf, 256); + + /* Check if the pair is a tag or whether it has data */ + if (ecs_get(world, rel, EcsComponent) == NULL) { + /* If pair has no data associated with it, use the + * component to which the pair has been added */ + component = ECS_PAIR_OBJECT(*pair); + } else { + component = rel; + } + } + } else { + + /* If the low part is a regular entity (component), then + * this query exactly matches a single pair instance. In + * this case we can simply do a lookup of the pair + * identifier in the table type. */ + result = ecs_type_index_of(table_type, 0, component); + } + } else { + /* Get column index for component */ + result = ecs_type_index_of(table_type, 0, component); + } + + /* If column is found, add one to the index, as column zero in + * a table is reserved for entity id's */ + if (result != -1) { + result ++; + } + + /* ecs_table_column_offset may return -1 if the component comes + * from a prefab. If so, the component will be resolved as a + * reference (see below) */ + } + + if (op == EcsAndFrom || op == EcsOrFrom || op == EcsNotFrom) { + result = 0; + } else if (op == EcsOptional) { + /* If table doesn't have the field, mark it as no data */ + if (!ecs_type_has_id(world, table_type, component, false)) { + result = 0; + } + } + + return result; +} + +static +ecs_vector_t* add_ref( + ecs_world_t *world, + ecs_query_t *query, + ecs_vector_t *references, + ecs_term_t *term, + ecs_entity_t component, + ecs_entity_t entity) +{ + ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); + ecs_term_id_t *subj = &term->args[0]; + + if (!(subj->set.mask & EcsCascade)) { + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + } + + *ref = (ecs_ref_t){0}; + ref->entity = entity; + ref->component = component; + + const EcsComponent *c_info = flecs_component_from_id(world, component); + if (c_info) { + if (c_info->size && subj->entity != 0) { + if (entity) { + ecs_get_ref_w_id(world, ref, entity, component); + } + + query->flags |= EcsQueryHasRefs; + } + } + + return references; +} + +static +int32_t get_pair_count( + ecs_type_t type, + ecs_entity_t pair) +{ + int32_t i = -1, result = 0; + while (-1 != (i = ecs_type_index_of(type, i + 1, pair))) { + result ++; + } + + return result; +} + +/* For each pair that the query subscribes for, count the occurrences in the + * table. Cardinality of subscribed for pairs must be the same as in the table + * or else the table won't match. */ +static +int32_t count_pairs( + const ecs_query_t *query, + ecs_type_t type) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + int32_t first_count = 0, pair_count = 0; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + + if (!ECS_HAS_ROLE(term->id, PAIR)) { + continue; + } + + if (term->args[0].entity != EcsThis) { + continue; + } + + if (ecs_id_is_wildcard(term->id)) { + pair_count = get_pair_count(type, term->id); + if (!first_count) { + first_count = pair_count; + } else { + if (first_count != pair_count) { + /* The pairs that this query subscribed for occur in the + * table but don't have the same cardinality. Ignore the + * table. This could typically happen for empty tables along + * a path in the table graph. */ + return -1; + } + } + } + } + + return first_count; +} + +static +ecs_type_t get_term_type( + ecs_world_t *world, + ecs_term_t *term, + ecs_entity_t component) +{ + ecs_oper_kind_t oper = term->oper; + + if (oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom) { + const EcsType *type = ecs_get(world, component, EcsType); + if (type) { + return type->normalized; + } else { + return ecs_get_type(world, component); + } + } else { + return ecs_type_from_id(world, component); + } +} + +/** Add table to system, compute offsets for system components in table it */ +static +void add_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_type_t table_type = NULL; + ecs_term_t *terms = query->filter.terms; + int32_t t, c, term_count = query->filter.term_count; + + if (table) { + table_type = table->type; + } + + int32_t pair_cur = 0, pair_count = count_pairs(query, table_type); + + /* If the query has pairs, we need to account for the fact that a table may + * have multiple components to which the pair is applied, which means the + * table has to be registered with the query multiple times, with different + * table columns. If so, allocate a small array for each pair in which the + * last added table index of the pair is stored, so that in the next + * iteration we can start the search from the correct offset type. */ + pair_offset_t *pair_offsets = NULL; + if (pair_count) { + pair_offsets = ecs_os_calloc( + ECS_SIZEOF(pair_offset_t) * term_count); + } + + /* From here we recurse */ + int32_t *table_indices = NULL; + int32_t table_indices_count = 0; + int32_t matched_table_index = 0; + ecs_matched_table_t table_data; + ecs_vector_t *references = NULL; + +add_pair: + table_data = (ecs_matched_table_t){ .table = table }; + if (table) { + table_type = table->type; + } + + /* If grouping is enabled for query, assign the group rank to the table */ + group_table(world, query, &table_data); + + if (term_count) { + /* Array that contains the system column to table column mapping */ + table_data.columns = ecs_os_calloc_n(int32_t, query->filter.term_count_actual); + ecs_assert(table_data.columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Store the components of the matched table. In the case of OR expressions, + * components may differ per matched table. */ + table_data.ids = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); + ecs_assert(table_data.ids != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Also cache types, so no lookup is needed while iterating */ + table_data.types = ecs_os_calloc_n(ecs_type_t, query->filter.term_count_actual); + ecs_assert(table_data.types != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Cache subject (source) entity ids for components */ + table_data.subjects = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); + ecs_assert(table_data.subjects != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Cache subject (source) entity ids for components */ + table_data.sizes = ecs_os_calloc_n(ecs_size_t, query->filter.term_count_actual); + ecs_assert(table_data.sizes != NULL, ECS_OUT_OF_MEMORY, NULL); + } + + /* Walk columns parsed from the system signature */ + c = 0; + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + ecs_term_id_t subj = term->args[0]; + ecs_entity_t entity = 0, component = 0; + ecs_oper_kind_t op = term->oper; + + if (op == EcsNot) { + subj.entity = 0; + } + + /* Get actual component and component source for current column */ + t = get_comp_and_src(world, query, t, table, &component, &entity); + + /* This column does not retrieve data from a static entity */ + if (!entity && subj.entity) { + int32_t index = get_component_index(world, table, table_type, + &component, c, op, pair_offsets, pair_cur + 1); + + if (index == -1) { + if (op == EcsOptional && subj.set.mask == EcsSelf) { + index = 0; + } + } else { + if (op == EcsOptional && !(subj.set.mask & EcsSelf)) { + index = 0; + } + } + + table_data.columns[c] = index; + + /* If the column is a case, we should only iterate the entities in + * the column for this specific case. Add a sparse column with the + * case id so we can find the correct entities when iterating */ + if (ECS_HAS_ROLE(component, CASE)) { + flecs_sparse_column_t *sc = ecs_vector_add( + &table_data.sparse_columns, flecs_sparse_column_t); + sc->signature_column_index = t; + sc->sw_case = component & ECS_COMPONENT_MASK; + sc->sw_column = NULL; + } + + /* If table has a disabled bitmask for components, check if there is + * a disabled column for the queried for component. If so, cache it + * in a vector as the iterator will need to skip the entity when the + * component is disabled. */ + if (index && (table && table->flags & EcsTableHasDisabled)) { + ecs_entity_t bs_id = + (component & ECS_COMPONENT_MASK) | ECS_DISABLED; + int32_t bs_index = ecs_type_index_of(table->type, 0, bs_id); + if (bs_index != -1) { + flecs_bitset_column_t *elem = ecs_vector_add( + &table_data.bitset_columns, flecs_bitset_column_t); + elem->column_index = bs_index; + elem->bs_column = NULL; + } + } + } + + ecs_entity_t type_id = ecs_get_typeid(world, component); + + if (entity || table_data.columns[c] == -1 || subj.set.mask & EcsCascade) { + if (type_id) { + references = add_ref(world, query, references, term, + component, entity); + table_data.columns[c] = -ecs_vector_count(references); + } + + table_data.subjects[c] = entity; + flecs_set_watch(world, entity); + } + + if (type_id) { + const EcsComponent *cptr = ecs_get(world, type_id, EcsComponent); + if (!cptr || !cptr->size) { + int32_t column = table_data.columns[c]; + if (column < 0) { + ecs_ref_t *r = ecs_vector_get( + references, ecs_ref_t, -column - 1); + r->component = 0; + } + } + + if (cptr) { + table_data.sizes[c] = cptr->size; + } else { + table_data.sizes[c] = 0; + } + } else { + table_data.sizes[c] = 0; + } + + + if (ECS_HAS_ROLE(component, SWITCH)) { + table_data.sizes[c] = ECS_SIZEOF(ecs_entity_t); + } else if (ECS_HAS_ROLE(component, CASE)) { + table_data.sizes[c] = ECS_SIZEOF(ecs_entity_t); + } + + table_data.ids[c] = component; + table_data.types[c] = get_term_type(world, term, component); + + c ++; + } + + /* Initially always add table to inactive group. If the system is registered + * with the table and the table is not empty, the table will send an + * activate signal to the system. */ + + ecs_matched_table_t *table_elem; + if (table && has_auto_activation(query)) { + table_elem = ecs_vector_add(&query->empty_tables, + ecs_matched_table_t); + + /* Store table index */ + matched_table_index = ecs_vector_count(query->empty_tables); + table_indices_count ++; + table_indices = ecs_os_realloc( + table_indices, table_indices_count * ECS_SIZEOF(int32_t)); + table_indices[table_indices_count - 1] = -matched_table_index; + + #ifndef NDEBUG + char *type_expr = ecs_type_str(world, table->type); + ecs_trace_2("query #[green]%s#[reset] matched with table #[green][%s]", + query_name(world, query), type_expr); + ecs_os_free(type_expr); + #endif + } else { + /* If no table is provided to function, this is a system that contains + * no columns that require table matching. In this case, the system will + * only have one "dummy" table that caches data from the system columns. + * Always add this dummy table to the list of active tables, since it + * would never get activated otherwise. */ + table_elem = ecs_vector_add(&query->tables, ecs_matched_table_t); + + /* If query doesn't automatically activates/inactivates tables, we can + * get the count to determine the current table index. */ + matched_table_index = ecs_vector_count(query->tables) - 1; + ecs_assert(matched_table_index >= 0, ECS_INTERNAL_ERROR, NULL); + } + + if (references) { + ecs_size_t ref_size = ECS_SIZEOF(ecs_ref_t) * ecs_vector_count(references); + table_data.references = ecs_os_malloc(ref_size); + ecs_os_memcpy(table_data.references, + ecs_vector_first(references, ecs_ref_t), ref_size); + ecs_vector_free(references); + references = NULL; + } + + *table_elem = table_data; + + /* Use tail recursion when adding table for multiple pairs */ + pair_cur ++; + if (pair_cur < pair_count) { + goto add_pair; + } + + /* Register table indices before sending out the match signal. This signal + * can cause table activation, and table indices are needed for that. */ + if (table_indices) { + ecs_table_indices_t *ti = ecs_map_ensure( + query->table_indices, ecs_table_indices_t, table->id); + if (ti->indices) { + ecs_os_free(ti->indices); + } + ti->indices = table_indices; + ti->count = table_indices_count; + } + + if (table && !(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query, + .matched_table_index = matched_table_index + }); + } else if (table && ecs_table_count(table)) { + activate_table(world, query, table, true); + } + + if (pair_offsets) { + ecs_os_free(pair_offsets); + } +} + +static +bool match_term( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_term_t *term, + ecs_match_failure_t *failure_info) +{ + (void)failure_info; + + ecs_term_id_t *subj = &term->args[0]; + + /* If term has no subject, there's nothing to match */ + if (!subj->entity) { + return true; + } + + if (term->args[0].entity != EcsThis) { + table = ecs_get_table(world, subj->entity); + } + + return ecs_type_match( + world, table, table->type, 0, term->id, subj->set.relation, + subj->set.min_depth, subj->set.max_depth, NULL) != -1; +} + +/* Match table with query */ +bool flecs_query_match( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_query_t *query, + ecs_match_failure_t *failure_info) +{ + /* Prevent having to add if not null checks everywhere */ + ecs_match_failure_t tmp_failure_info; + if (!failure_info) { + failure_info = &tmp_failure_info; + } + + failure_info->reason = EcsMatchOk; + failure_info->column = 0; + + if (!(query->flags & EcsQueryNeedsTables)) { + failure_info->reason = EcsMatchSystemIsATask; + return false; + } + + ecs_type_t table_type = table->type; + + /* Don't match disabled entities */ + if (!(query->flags & EcsQueryMatchDisabled) && ecs_type_has_id( + world, table_type, EcsDisabled, true)) + { + failure_info->reason = EcsMatchEntityIsDisabled; + return false; + } + + /* Don't match prefab entities */ + if (!(query->flags & EcsQueryMatchPrefab) && ecs_type_has_id( + world, table_type, EcsPrefab, true)) + { + failure_info->reason = EcsMatchEntityIsPrefab; + return false; + } + + /* Check if pair cardinality matches pairs in query, if any */ + if (count_pairs(query, table->type) == -1) { + return false; + } + + ecs_term_t *terms = query->filter.terms; + int32_t i, term_count = query->filter.term_count; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_oper_kind_t oper = term->oper; + + failure_info->column = i + 1; + + if (oper == EcsAnd) { + if (!match_term(world, table, term, failure_info)) { + return false; + } + + } else if (oper == EcsNot) { + if (match_term(world, table, term, failure_info)) { + return false; + } + + } else if (oper == EcsOr) { + bool match = false; + + for (; i < term_count; i ++) { + term = &terms[i]; + if (term->oper != EcsOr) { + i --; + break; + } + + if (!match && match_term( + world, table, term, failure_info)) + { + match = true; + } + } + + if (!match) { + return false; + } + + } else if (oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom) { + ecs_type_t type = get_term_type((ecs_world_t*)world, term, term->id); + int32_t match_count = 0, j, count = ecs_vector_count(type); + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + + for (j = 0; j < count; j ++) { + ecs_term_t tmp_term = *term; + tmp_term.oper = EcsAnd; + tmp_term.id = ids[j]; + tmp_term.pred.entity = ids[j]; + + if (match_term(world, table, &tmp_term, failure_info)) { + match_count ++; + } + } + + if (oper == EcsAndFrom && match_count != count) { + return false; + } + if (oper == EcsOrFrom && match_count == 0) { + return false; + } + if (oper == EcsNotFrom && match_count != 0) { + return false; + } + } + } + + return true; +} + +/** Match existing tables against system (table is created before system) */ +static +void match_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + int32_t i, count = flecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense( + world->store.tables, ecs_table_t, i); + + if (flecs_query_match(world, table, query, NULL)) { + add_table(world, query, table); + } + } + + order_grouped_tables(world, query); +} + +#define ELEM(ptr, size, index) ECS_OFFSET(ptr, size * index) + +static +int32_t qsort_partition( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t elem_size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t compare) +{ + int32_t p = (hi + lo) / 2; + void *pivot = 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 = ELEM(ptr, elem_size, i); + } while ( compare(entities[i], el, pivot_e, pivot) < 0); + + do { + j --; + el = ELEM(ptr, elem_size, j); + } while ( compare(entities[j], el, pivot_e, pivot) > 0); + + if (i >= j) { + return j; + } + + flecs_table_swap(world, table, data, i, j); + + if (p == i) { + pivot = ELEM(ptr, elem_size, j); + pivot_e = entities[j]; + } else if (p == j) { + pivot = ELEM(ptr, elem_size, i); + pivot_e = entities[i]; + } + + goto repeat; + } +} + +static +void qsort_array( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t compare) +{ + if ((hi - lo) < 1) { + return; + } + + int32_t p = qsort_partition( + world, table, data, entities, ptr, size, lo, hi, compare); + + qsort_array(world, table, data, entities, ptr, size, lo, p, compare); + + qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare); +} + +static +void sort_table( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare) +{ + ecs_data_t *data = flecs_table_get_data(table); + if (!data || !data->entities) { + /* Nothing to sort */ + return; + } + + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &data->columns[column_index]; + size = column->size; + ptr = ecs_vector_first_t(column->data, size, column->alignment); + } + + qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); +} + +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_matched_table_t *table; + 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 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 build_sorted_table_range( + ecs_query_t *query, + int32_t start, + int32_t end) +{ + ecs_world_t *world = query->world; + ecs_entity_t component = query->order_by_component; + ecs_order_by_action_t compare = query->order_by; + + /* Fetch data from all matched tables */ + ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t); + sort_helper_t *helper = ecs_os_malloc((end - start) * ECS_SIZEOF(sort_helper_t)); + + int i, to_sort = 0; + for (i = start; i < end; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + ecs_data_t *data = flecs_table_get_data(table); + ecs_vector_t *entities; + if (!data || !(entities = data->entities) || !ecs_table_count(table)) { + continue; + } + + int32_t index = ecs_type_index_of(table->type, 0, component); + if (index != -1) { + ecs_column_t *column = &data->columns[index]; + int16_t size = column->size; + int16_t align = column->alignment; + helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else if (component) { + /* Find component in prefab */ + ecs_entity_t base; + ecs_type_match(world, table, table->type, 0, component, + EcsIsA, 1, 0, &base); + + /* If a base was not found, the query should not have allowed using + * the component for sorting */ + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *cptr = ecs_get(world, component, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + + helper[to_sort].ptr = ecs_get_id(world, base, component); + helper[to_sort].elem_size = cptr->size; + helper[to_sort].shared = true; + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; + } + + helper[to_sort].table = table_data; + helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } + + ecs_table_slice_t *cur = NULL; + + bool proceed; + do { + int32_t j, min = 0; + proceed = true; + + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } + } + + 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->table != cur_helper->table) { + cur = ecs_vector_add(&query->table_slices, ecs_table_slice_t); + ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); + cur->table = cur_helper->table; + cur->start_row = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; + } + + cur_helper->row ++; + } while (proceed); + + ecs_os_free(helper); +} + +static +void build_sorted_tables( + ecs_query_t *query) +{ + /* Clean previous sorted tables */ + ecs_vector_free(query->table_slices); + query->table_slices = NULL; + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t); + ecs_matched_table_t *table = NULL; + + int32_t start = 0, rank = 0; + for (i = 0; i < count; i ++) { + table = &tables[i]; + if (rank != table->rank) { + if (start != i) { + build_sorted_table_range(query, start, i); + start = i; + } + rank = table->rank; + } + } + + if (start != i) { + build_sorted_table_range(query, start, i); + } +} + +static +bool tables_dirty( + ecs_query_t *query) +{ + if (query->needs_reorder) { + order_grouped_tables(query->world, query); + } + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first(query->tables, + ecs_matched_table_t); + bool is_dirty = false; + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + + if (!table_data->monitor) { + table_data->monitor = flecs_table_get_monitor(table); + is_dirty = true; + } + + int32_t *dirty_state = flecs_table_get_dirty_state(table); + int32_t t, type_count = table->column_count; + for (t = 0; t < type_count + 1; t ++) { + is_dirty = is_dirty || (dirty_state[t] != table_data->monitor[t]); + } + } + + is_dirty = is_dirty || (query->match_count != query->prev_match_count); + + return is_dirty; +} + +static +void tables_reset_dirty( + ecs_query_t *query) +{ + query->prev_match_count = query->match_count; + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + + if (!table_data->monitor) { + /* If one table doesn't have a monitor, none of the tables will have + * a monitor, so early out. */ + return; + } + + int32_t *dirty_state = flecs_table_get_dirty_state(table); + int32_t t, type_count = table->column_count; + for (t = 0; t < type_count + 1; t ++) { + table_data->monitor[t] = dirty_state[t]; + } + } +} + +static +void sort_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_order_by_action_t compare = query->order_by; + if (!compare) { + return; + } + + ecs_entity_t order_by_component = query->order_by_component; + + /* Iterate over active tables. Don't bother with inactive tables, since + * they're empty */ + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + bool tables_sorted = false; + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + + /* If no monitor had been created for the table yet, create it now */ + bool is_dirty = false; + if (!table_data->monitor) { + table_data->monitor = flecs_table_get_monitor(table); + + /* A new table is always dirty */ + is_dirty = true; + } + + int32_t *dirty_state = flecs_table_get_dirty_state(table); + + is_dirty = is_dirty || (dirty_state[0] != table_data->monitor[0]); + + int32_t index = -1; + if (order_by_component) { + /* Get index of sorted component. We only care if the component we're + * sorting on has changed or if entities have been added / re(moved) */ + index = ecs_type_index_of(table->type, 0, order_by_component); + if (index != -1) { + ecs_assert(index < ecs_vector_count(table->type), ECS_INTERNAL_ERROR, NULL); + is_dirty = is_dirty || (dirty_state[index + 1] != table_data->monitor[index + 1]); + } else { + /* Table does not contain component which means the sorted + * component is shared. Table does not need to be sorted */ + continue; + } + } + + /* Check both if entities have moved (element 0) or if the component + * we're sorting on has changed (index + 1) */ + if (is_dirty) { + /* Sort the table */ + sort_table(world, table, index, compare); + tables_sorted = true; + } + } + + if (tables_sorted || query->match_count != query->prev_match_count) { + build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ + } +} + +static +bool has_refs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + + if (term->oper == EcsNot && !subj->entity) { + /* Special case: if oper kind is Not and the query contained a + * shared expression, the expression is translated to FromEmpty to + * prevent resolving the ref */ + return true; + } else if (subj->entity && (subj->entity != EcsThis || subj->set.mask != EcsSelf)) { + /* If entity is not this, or if it can be substituted by other + * entities, the query can have references. */ + return true; + } + } + + return false; +} + +static +bool has_pairs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + if (ecs_id_is_wildcard(terms[i].id)) { + return true; + } + } + + return false; +} + +static +void register_monitors( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + + /* If component is requested with EcsCascade register component as a + * parent monitor. Parent monitors keep track of whether an entity moved + * in the hierarchy, which potentially requires the query to reorder its + * tables. + * Also register a regular component monitor for EcsCascade columns. + * This ensures that when the component used in the EcsCascade column + * is added or removed tables are updated accordingly*/ + if (subj->set.mask & EcsSuperSet && subj->set.mask & EcsCascade && + subj->set.relation != EcsIsA) + { + if (term->oper != EcsOr) { + if (term->args[0].set.relation != EcsIsA) { + flecs_monitor_register( + world, term->args[0].set.relation, term->id, query); + } + flecs_monitor_register(world, 0, term->id, query); + } + + /* FromAny also requires registering a monitor, as FromAny columns can + * be matched with prefabs. The only term kinds that do not require + * registering a monitor are FromOwned and FromEmpty. */ + } else if ((subj->set.mask & EcsSuperSet) || (subj->entity != EcsThis)){ + if (term->oper != EcsOr) { + flecs_monitor_register(world, 0, term->id, query); + } + } + }; +} + +static +void process_signature( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *pred = &term->pred; + ecs_term_id_t *subj = &term->args[0]; + ecs_term_id_t *obj = &term->args[1]; + ecs_oper_kind_t op = term->oper; + ecs_inout_kind_t inout = term->inout; + + (void)pred; + (void)obj; + + /* Queries do not support variables */ + ecs_assert(pred->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + ecs_assert(subj->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + ecs_assert(obj->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + + /* Queries do not support subset substitutions */ + ecs_assert(!(pred->set.mask & EcsSubSet), ECS_UNSUPPORTED, NULL); + ecs_assert(!(subj->set.mask & EcsSubSet), ECS_UNSUPPORTED, NULL); + ecs_assert(!(obj->set.mask & EcsSubSet), ECS_UNSUPPORTED, NULL); + + /* Superset/subset substitutions aren't supported for pred/obj */ + ecs_assert(pred->set.mask == EcsDefaultSet, ECS_UNSUPPORTED, NULL); + ecs_assert(obj->set.mask == EcsDefaultSet, ECS_UNSUPPORTED, NULL); + + if (subj->set.mask == EcsDefaultSet) { + subj->set.mask = EcsSelf; + } + + /* If self is not included in set, always start from depth 1 */ + if (!subj->set.min_depth && !(subj->set.mask & EcsSelf)) { + subj->set.min_depth = 1; + } + + if (inout != EcsIn) { + query->flags |= EcsQueryHasOutColumns; + } + + if (op == EcsOptional) { + query->flags |= EcsQueryHasOptional; + } + + if (!(query->flags & EcsQueryMatchDisabled)) { + if (op == EcsAnd || op == EcsOr || op == EcsOptional) { + if (term->id == EcsDisabled) { + query->flags |= EcsQueryMatchDisabled; + } + } + } + + if (!(query->flags & EcsQueryMatchPrefab)) { + if (op == EcsAnd || op == EcsOr || op == EcsOptional) { + if (term->id == EcsPrefab) { + query->flags |= EcsQueryMatchPrefab; + } + } + } + + if (subj->entity == EcsThis) { + query->flags |= EcsQueryNeedsTables; + } + + if (subj->set.mask & EcsCascade && term->oper == EcsOptional) { + /* Query can only have one cascade column */ + ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); + query->cascade_by = i + 1; + } + + if (subj->entity && subj->entity != EcsThis && + subj->set.mask == EcsSelf) + { + flecs_set_watch(world, term->args[0].entity); + } + } + + query->flags |= (ecs_flags32_t)(has_refs(query) * EcsQueryHasRefs); + query->flags |= (ecs_flags32_t)(has_pairs(query) * EcsQueryHasTraits); + + if (!(query->flags & EcsQueryIsSubquery)) { + register_monitors(world, query); + } +} + +static +bool match_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + if (flecs_query_match(world, table, query, NULL)) { + add_table(world, query, table); + return true; + } + return false; +} + +/* Move table from empty to non-empty list, or vice versa */ +static +int32_t move_table( + ecs_query_t *query, + ecs_table_t *table, + int32_t index, + ecs_vector_t **dst_array, + ecs_vector_t *src_array, + bool activate) +{ + (void)table; + + int32_t new_index = 0; + int32_t last_src_index = ecs_vector_count(src_array) - 1; + ecs_assert(last_src_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_matched_table_t *mt = ecs_vector_last(src_array, ecs_matched_table_t); + + /* The last table of the source array will be moved to the location of the + * table to move, do some bookkeeping to keep things consistent. */ + if (last_src_index) { + ecs_table_indices_t *ti = ecs_map_get(query->table_indices, + ecs_table_indices_t, mt->table->id); + + int i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t old_index = ti->indices[i]; + if (activate) { + if (old_index >= 0) { + /* old_index should be negative if activate is true, since + * we're moving from the empty list to the non-empty list. + * However, if the last table in the source array is also + * the table being moved, this can happen. */ + ecs_assert(table == mt->table, + ECS_INTERNAL_ERROR, NULL); + continue; + } + /* If activate is true, src = the empty list, and index should + * be negative. */ + old_index = old_index * -1 - 1; /* Normalize */ + } + + /* Ensure to update correct index, as there can be more than one */ + if (old_index == last_src_index) { + if (activate) { + ti->indices[i] = index * -1 - 1; + } else { + ti->indices[i] = index; + } + break; + } + } + + /* If the src array contains tables, there must be a table that will get + * moved. */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); + } else { + /* If last_src_index is 0, the table to move was the only table in the + * src array, so no other administration needs to be updated. */ + } + + /* Actually move the table. Only move from src to dst if we have a + * dst_array, otherwise just remove it from src. */ + if (dst_array) { + new_index = ecs_vector_count(*dst_array); + ecs_vector_move_index(dst_array, src_array, ecs_matched_table_t, index); + + /* Make sure table is where we expect it */ + mt = ecs_vector_last(*dst_array, ecs_matched_table_t); + ecs_assert(mt->table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(*dst_array) == (new_index + 1), + ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vector_remove(src_array, ecs_matched_table_t, index); + } + + /* Ensure that src array has now one element less */ + ecs_assert(ecs_vector_count(src_array) == last_src_index, + ECS_INTERNAL_ERROR, NULL); + + /* Return new index for table */ + if (activate) { + /* Table is now active, index is positive */ + return new_index; + } else { + /* Table is now inactive, index is negative */ + return new_index * -1 - 1; + } +} + +/** Table activation happens when a table was or becomes empty. Deactivated + * tables are not considered by the system in the main loop. */ +static +void activate_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table, + bool active) +{ + ecs_vector_t *src_array, *dst_array; + int32_t activated = 0; + int32_t prev_dst_count = 0; + (void)world; + (void)prev_dst_count; /* Only used when built with systems module */ + + if (active) { + src_array = query->empty_tables; + dst_array = query->tables; + prev_dst_count = ecs_vector_count(dst_array); + } else { + src_array = query->tables; + dst_array = query->empty_tables; + } + + ecs_table_indices_t *ti = ecs_map_get( + query->table_indices, ecs_table_indices_t, table->id); + + if (ti) { + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t index = ti->indices[i]; + + if (index < 0) { + if (!active) { + /* If table is already inactive, no need to move */ + continue; + } + index = index * -1 - 1; + } else { + if (active) { + /* If table is already active, no need to move */ + continue; + } + } + + ecs_matched_table_t *mt = ecs_vector_get( + src_array, ecs_matched_table_t, index); + ecs_assert(mt->table == table, ECS_INTERNAL_ERROR, NULL); + (void)mt; + + activated ++; + + ti->indices[i] = move_table( + query, table, index, &dst_array, src_array, active); + } + + if (activated) { + /* Activate system if registered with query */ +#ifdef FLECS_SYSTEMS_H + if (query->system) { + int32_t dst_count = ecs_vector_count(dst_array); + if (active) { + if (!prev_dst_count && dst_count) { + ecs_system_activate(world, query->system, true, NULL); + } + } else if (ecs_vector_count(src_array) == 0) { + ecs_system_activate(world, query->system, false, NULL); + } + } +#endif + } + + if (active) { + query->tables = dst_array; + } else { + query->empty_tables = dst_array; + } + } + + if (!activated) { + /* Received an activate event for a table we're not matched with. This + * can only happen if this is a subquery */ + ecs_assert((query->flags & EcsQueryIsSubquery) != 0, + ECS_INTERNAL_ERROR, NULL); + return; + } + + /* Signal query it needs to reorder tables. Doing this in place could slow + * down scenario's where a large number of tables is matched with an ordered + * query. Since each table would trigger the activate signal, there would be + * as many sorts as added tables, vs. only one when ordering happens when an + * iterator is obtained. */ + query->needs_reorder = true; +} + +static +void add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) +{ + ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); + *elem = subquery; + + /* Iterate matched tables, match them with subquery */ + ecs_matched_table_t *tables = ecs_vector_first(parent->tables, ecs_matched_table_t); + int32_t i, count = ecs_vector_count(parent->tables); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table = &tables[i]; + match_table(world, subquery, table->table); + activate_table(world, subquery, table->table, true); + } + + /* Do the same for inactive tables */ + tables = ecs_vector_first(parent->empty_tables, ecs_matched_table_t); + count = ecs_vector_count(parent->empty_tables); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table = &tables[i]; + match_table(world, subquery, table->table); + } +} + +static +void notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + if (query->subqueries) { + ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); + int32_t i, count = ecs_vector_count(query->subqueries); + + ecs_query_event_t sub_event = *event; + sub_event.parent_query = query; + + for (i = 0; i < count; i ++) { + ecs_query_t *sub = queries[i]; + flecs_query_notify(world, sub, &sub_event); + } + } +} + +static +void free_matched_table( + ecs_matched_table_t *table) +{ + ecs_os_free(table->columns); + ecs_os_free(table->ids); + ecs_os_free(table->subjects); + ecs_os_free(table->sizes); + ecs_os_free((ecs_vector_t**)table->types); + ecs_os_free(table->references); + ecs_os_free(table->sparse_columns); + ecs_os_free(table->bitset_columns); + ecs_os_free(table->monitor); +} + +/** Check if a table was matched with the system */ +static +ecs_table_indices_t* get_table_indices( + ecs_query_t *query, + ecs_table_t *table) +{ + return ecs_map_get(query->table_indices, ecs_table_indices_t, table->id); +} + +static +void resolve_cascade_subject( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_indices_t *ti, + const ecs_table_t *table, + ecs_type_t table_type) +{ + int32_t term_index = query->cascade_by - 1; + ecs_term_t *term = &query->filter.terms[term_index]; + + /* For each table entry, find the correct subject of a cascade term */ + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t table_data_index = ti->indices[i]; + ecs_matched_table_t *table_data; + + if (table_data_index >= 0) { + table_data = ecs_vector_get( + query->tables, ecs_matched_table_t, table_data_index); + } else { + table_data = ecs_vector_get( + query->empty_tables, ecs_matched_table_t, + -1 * table_data_index - 1); + } + + ecs_assert(table_data->references != 0, + ECS_INTERNAL_ERROR, NULL); + + /* Obtain reference index */ + int32_t *column_indices = table_data->columns; + int32_t ref_index = -column_indices[term_index] - 1; + + /* Obtain pointer to the reference data */ + ecs_ref_t *references = table_data->references; + + /* Find source for component */ + ecs_entity_t subject; + ecs_type_match(world, table, table_type, 0, term->id, + term->args[0].set.relation, 1, 0, &subject); + + /* If container was found, update the reference */ + if (subject) { + ecs_ref_t *ref = &references[ref_index]; + ecs_assert(ref->component == term->id, ECS_INTERNAL_ERROR, NULL); + + references[ref_index].entity = ecs_get_alive(world, subject); + table_data->subjects[term_index] = subject; + ecs_get_ref_w_id(world, ref, subject, term->id); + } else { + references[ref_index].entity = 0; + table_data->subjects[term_index] = 0; + } + } +} + +/* Remove table */ +static +void remove_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_vector_t *tables, + int32_t index, + bool empty) +{ + ecs_matched_table_t *mt = ecs_vector_get( + tables, ecs_matched_table_t, index); + if (!mt) { + /* Query was notified of a table it doesn't match with, this can only + * happen if query is a subquery. */ + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_assert(mt->table == table, ECS_INTERNAL_ERROR, NULL); + (void)table; + + /* Free table before moving, as the move will cause another table to occupy + * the memory of mt */ + free_matched_table(mt); + move_table(query, mt->table, index, NULL, tables, empty); +} + +static +void unmatch_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_table_indices_t *ti) +{ + if (!ti) { + ti = get_table_indices(query, table); + if (!ti) { + return; + } + } + + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t index = ti->indices[i]; + if (index < 0) { + index = index * -1 - 1; + remove_table(query, table, query->empty_tables, index, true); + } else { + remove_table(query, table, query->tables, index, false); + } + } + + ecs_os_free(ti->indices); + ecs_map_remove(query->table_indices, table->id); +} + +static +void rematch_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_table_indices_t *match = get_table_indices(query, table); + + if (flecs_query_match(world, table, query, NULL)) { + /* If the table matches, and it is not currently matched, add */ + if (match == NULL) { + add_table(world, query, table); + + /* If table still matches and has cascade column, reevaluate the + * sources of references. This may have changed in case + * components were added/removed to container entities */ + } else if (query->cascade_by) { + resolve_cascade_subject(world, query, match, table, table->type); + + /* If query has optional columns, it is possible that a column that + * previously had data no longer has data, or vice versa. Do a full + * rematch to make sure data is consistent. */ + } else if (query->flags & EcsQueryHasOptional) { + unmatch_table(query, table, match); + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + add_table(world, query, table); + } + } else { + /* Table no longer matches, remove */ + if (match != NULL) { + unmatch_table(query, table, match); + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + } + } +} + +static +bool satisfy_constraints( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t oper = term->oper; + + if (subj->entity != EcsThis && subj->set.mask & EcsSelf) { + ecs_type_t type = ecs_get_type(world, subj->entity); + + if (ecs_type_has_id(world, type, term->id, false)) { + if (oper == EcsNot) { + return false; + } + } else { + if (oper != EcsNot) { + return false; + } + } + } + } + + return true; +} + +/* Rematch system with tables after a change happened to a watched entity */ +static +void rematch_tables( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_t *parent_query) +{ + if (parent_query) { + ecs_matched_table_t *tables = ecs_vector_first(parent_query->tables, ecs_matched_table_t); + int32_t i, count = ecs_vector_count(parent_query->tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i].table; + rematch_table(world, query, table); + } + + tables = ecs_vector_first(parent_query->empty_tables, ecs_matched_table_t); + count = ecs_vector_count(parent_query->empty_tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i].table; + rematch_table(world, query, table); + } + } else { + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + /* Is the system currently matched with the table? */ + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + rematch_table(world, query, table); + } + } + + group_tables(world, query); + order_grouped_tables(world, query); + + /* Enable/disable system if constraints are (not) met. If the system is + * already dis/enabled this operation has no side effects. */ + query->constraints_satisfied = satisfy_constraints(world, &query->filter); +} + +static +void remove_subquery( + ecs_query_t *parent, + ecs_query_t *sub) +{ + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vector_count(parent->subqueries); + ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); + + for (i = 0; i < count; i ++) { + if (sq[i] == sub) { + break; + } + } + + ecs_vector_remove(parent->subqueries, ecs_query_t*, i); +} + +/* -- Private API -- */ + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + bool notify = true; + + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + if (match_table(world, query, event->table)) { + if (query->subqueries) { + notify_subqueries(world, query, event); + } + } + notify = false; + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + unmatch_table(query, event->table, NULL); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + rematch_tables(world, query, event->parent_query); + break; + case EcsQueryTableEmpty: + /* Table is empty, deactivate */ + activate_table(world, query, event->table, false); + break; + case EcsQueryTableNonEmpty: + /* Table is non-empty, activate */ + activate_table(world, query, event->table, true); + break; + case EcsQueryOrphan: + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + query->flags |= EcsQueryIsOrphaned; + query->parent = NULL; + break; + } + + if (notify) { + notify_subqueries(world, query, event); + } +} + +void ecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t order_by_component, + ecs_order_by_action_t order_by) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + + query->order_by_component = order_by_component; + query->order_by = order_by; + + ecs_vector_free(query->table_slices); + query->table_slices = NULL; + + sort_tables(world, query); + + if (!query->table_slices) { + build_sorted_tables(query); + } +} + +void ecs_query_group_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + + query->group_by_id = sort_component; + query->group_by = group_by; + + group_tables(world, query); + + order_grouped_tables(world, query); + + build_sorted_tables(query); +} + + +/* -- Public API -- */ + +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + ecs_query_t *result = flecs_sparse_add(world->queries, ecs_query_t); + result->id = flecs_sparse_last_id(world->queries); + + if (ecs_filter_init(world, &result->filter, &desc->filter)) { + flecs_sparse_remove(world->queries, result->id); + return NULL; + } + + result->world = world; + result->table_indices = ecs_map_new(ecs_table_indices_t, 0); + result->tables = ecs_vector_new(ecs_matched_table_t, 0); + result->empty_tables = ecs_vector_new(ecs_matched_table_t, 0); + result->system = desc->system; + result->prev_match_count = -1; + + if (desc->parent != NULL) { + result->flags |= EcsQueryIsSubquery; + } + + /* If a system is specified, ensure that if there are any subjects in the + * filter that refer to the system, the component is added */ + if (desc->system) { + int32_t t, term_count = result->filter.term_count; + ecs_term_t *terms = result->filter.terms; + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->args[0].entity == desc->system) { + ecs_add_id(world, desc->system, term->id); + } + } + } + + process_signature(world, result); + + ecs_trace_2("query #[green]%s#[reset] created with expression #[red]%s", + query_name(world, result), result->filter.expr); + + ecs_log_push(); + + if (!desc->parent) { + if (result->flags & EcsQueryNeedsTables) { + if (desc->system) { + if (ecs_has_id(world, desc->system, EcsMonitor)) { + result->flags |= EcsQueryMonitor; + } + + if (ecs_has_id(world, desc->system, EcsOnSet)) { + result->flags |= EcsQueryOnSet; + } + + if (ecs_has_id(world, desc->system, EcsUnSet)) { + result->flags |= EcsQueryUnSet; + } + } + + match_tables(world, result); + } else { + /* Add stub table that resolves references (if any) so everything is + * preprocessed when the query is evaluated. */ + add_table(world, result, NULL); + } + } else { + add_subquery(world, desc->parent, result); + result->parent = desc->parent; + } + + result->constraints_satisfied = satisfy_constraints(world, &result->filter); + + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + result->group_by = group_by_cascade; + result->group_by_id = result->filter.terms[cascade_by - 1].id; + result->group_by_ctx = &result->filter.terms[cascade_by - 1]; + } + + if (desc->order_by) { + ecs_query_order_by( + world, result, desc->order_by_component, desc->order_by); + } + + if (desc->group_by) { + /* Can't have a cascade term and group by at the same time, as cascade + * uses the group_by mechanism */ + ecs_assert(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); + ecs_query_group_by(world, result, desc->group_by_id, desc->group_by); + result->group_by_ctx = desc->group_by_ctx; + result->group_by_ctx_free = desc->group_by_ctx_free; + } + + ecs_log_pop(); + + return result; +} + +void ecs_query_fini( + ecs_query_t *query) +{ + ecs_world_t *world = query->world; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + + if (query->group_by_ctx_free) { + if (query->group_by_ctx) { + query->group_by_ctx_free(query->group_by_ctx); + } + } + + if ((query->flags & EcsQueryIsSubquery) && + !(query->flags & EcsQueryIsOrphaned)) + { + remove_subquery(query->parent, query); + } + + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); + + ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, { + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table->table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + free_matched_table(table); + }); + + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table->table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + free_matched_table(table); + }); + + ecs_map_iter_t it = ecs_map_iter(query->table_indices); + ecs_table_indices_t *ti; + while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) { + ecs_os_free(ti->indices); + } + + ecs_map_free(query->table_indices); + ecs_vector_free(query->subqueries); + ecs_vector_free(query->tables); + ecs_vector_free(query->empty_tables); + ecs_vector_free(query->table_slices); + ecs_filter_fini(&query->filter); + + /* Remove query from storage */ + flecs_sparse_remove(world->queries, query->id); +} + +const ecs_filter_t* ecs_query_get_filter( + ecs_query_t *query) +{ + return &query->filter; +} + +/* Create query iterator */ +ecs_iter_t ecs_query_iter_page( + ecs_query_t *query, + int32_t offset, + int32_t limit) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + + ecs_world_t *world = query->world; + + if (query->needs_reorder) { + order_grouped_tables(world, query); + } + + sort_tables(world, query); + + if (!world->is_readonly && query->flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(world); + } + + tables_reset_dirty(query); + + int32_t table_count; + if (query->table_slices) { + table_count = ecs_vector_count(query->table_slices); + } else { + table_count = ecs_vector_count(query->tables); + } + + ecs_query_iter_t it = { + .page_iter = { + .offset = offset, + .limit = limit, + .remaining = limit + }, + .index = 0, + }; + + return (ecs_iter_t){ + .world = world, + .query = query, + .column_count = query->filter.term_count_actual, + .table_count = table_count, + .inactive_table_count = ecs_vector_count(query->empty_tables), + .iter.query = it + }; +} + +ecs_iter_t ecs_query_iter( + ecs_query_t *query) +{ + return ecs_query_iter_page(query, 0, 0); +} + +static +void populate_ptrs( + ecs_world_t *world, + ecs_iter_t *it) +{ + ecs_table_t *table = it->table; + const ecs_data_t *data = NULL; + ecs_column_t *columns = NULL; + ecs_id_t *ids = NULL; + + if (table) { + data = flecs_table_get_data(table); + ids = ecs_vector_first(table->type, ecs_id_t); + } + if (data) { + columns = data->columns; + } + + int c; + for (c = 0; c < it->column_count; c ++) { + int32_t c_index = it->columns[c]; + if (!c_index) { + it->ptrs[c] = NULL; + continue; + } + + if (c_index > 0) { + c_index --; + + if (!columns) { + continue; + } + + if (it->sizes[c] == 0) { + continue; + } + + ecs_vector_t *vec; + ecs_size_t size, align; + if (ECS_HAS_ROLE(ids[c_index], SWITCH)) { + ecs_switch_t *sw = data->sw_columns[ + c_index - table->sw_column_offset].data; + vec = flecs_switch_values(sw); + size = ECS_SIZEOF(ecs_entity_t); + align = ECS_ALIGNOF(ecs_entity_t); + } else { + ecs_column_t *col = &columns[c_index]; + vec = col->data; + size = col->size; + align = col->alignment; + } + + it->ptrs[c] = ecs_vector_get_t(vec, size, align, it->offset); + } else { + ecs_ref_t *ref = &it->references[-c_index - 1]; + char buf[255]; ecs_id_str(world, ref->component, buf, 255); + it->ptrs[c] = (void*)ecs_get_ref_w_id( + world, ref, ref->entity, ref->component); + } + } +} + +void flecs_query_set_iter( + ecs_world_t *world, + ecs_query_t *query, + ecs_iter_t *it, + int32_t table_index, + int32_t row, + int32_t count) +{ + (void)world; + + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + + ecs_matched_table_t *table_data = ecs_vector_get( + query->tables, ecs_matched_table_t, table_index); + ecs_assert(table_data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = table_data->table; + ecs_data_t *data = flecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entity_buffer = ecs_vector_first(data->entities, ecs_entity_t); + it->entities = &entity_buffer[row]; + + it->world = NULL; + it->query = query; + it->column_count = query->filter.term_count_actual; + it->table_count = 1; + it->inactive_table_count = 0; + it->table_columns = data->columns; + it->table = table; + it->ids = table_data->ids; + it->columns = table_data->columns; + it->types = table_data->types; + it->subjects = table_data->subjects; + it->sizes = table_data->sizes; + it->references = table_data->references; + it->offset = row; + it->count = count; + it->total_count = count; + + ecs_iter_init(it); + + populate_ptrs(world, it); +} + +static +int ecs_page_iter_next( + ecs_page_iter_t *it, + ecs_page_cursor_t *cur) +{ + int32_t offset = it->offset; + int32_t limit = it->limit; + if (!(offset || limit)) { + return cur->count == 0; + } + + int32_t count = cur->count; + int32_t remaining = it->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + it->offset -= count; + return 1; + } else { + cur->first += offset; + count = cur->count -= offset; + it->offset = 0; + } + } + + if (remaining) { + if (remaining > count) { + it->remaining -= count; + } else { + count = cur->count = remaining; + it->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + return -1; + } + + return count == 0; +} + +static +int find_smallest_column( + ecs_table_t *table, + ecs_matched_table_t *table_data, + ecs_vector_t *sparse_columns) +{ + flecs_sparse_column_t *sparse_column_array = + ecs_vector_first(sparse_columns, flecs_sparse_column_t); + int32_t i, count = ecs_vector_count(sparse_columns); + int32_t min = INT_MAX, index = 0; + + for (i = 0; i < count; i ++) { + /* The array with sparse queries for the matched table */ + flecs_sparse_column_t *sparse_column = &sparse_column_array[i]; + + /* Pointer to the switch column struct of the table */ + ecs_sw_column_t *sc = sparse_column->sw_column; + + /* If the sparse column pointer hadn't been retrieved yet, do it now */ + if (!sc) { + /* Get the table column index from the signature column index */ + int32_t table_column_index = table_data->columns[ + sparse_column->signature_column_index]; + + /* Translate the table column index to switch column index */ + table_column_index -= table->sw_column_offset; + ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + + /* Get the sparse column */ + ecs_data_t *data = flecs_table_get_data(table); + sc = sparse_column->sw_column = + &data->sw_columns[table_column_index - 1]; + } + + /* Find the smallest column */ + ecs_switch_t *sw = sc->data; + int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); + if (case_count < min) { + min = case_count; + index = i + 1; + } + } + + return index; +} + +static +int sparse_column_next( + ecs_table_t *table, + ecs_matched_table_t *matched_table, + ecs_vector_t *sparse_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) +{ + bool first_iteration = false; + int32_t sparse_smallest; + + if (!(sparse_smallest = iter->sparse_smallest)) { + sparse_smallest = iter->sparse_smallest = find_smallest_column( + table, matched_table, sparse_columns); + first_iteration = true; + } + + sparse_smallest -= 1; + + flecs_sparse_column_t *columns = ecs_vector_first( + sparse_columns, flecs_sparse_column_t); + flecs_sparse_column_t *column = &columns[sparse_smallest]; + ecs_switch_t *sw, *sw_smallest = column->sw_column->data; + ecs_entity_t case_smallest = column->sw_case; + + /* Find next entity to iterate in sparse column */ + int32_t first; + if (first_iteration) { + first = flecs_switch_first(sw_smallest, case_smallest); + } else { + first = flecs_switch_next(sw_smallest, iter->sparse_first); + } + + if (first == -1) { + goto done; + } + + /* Check if entity matches with other sparse columns, if any */ + int32_t i, count = ecs_vector_count(sparse_columns); + do { + for (i = 0; i < count; i ++) { + if (i == sparse_smallest) { + /* Already validated this one */ + continue; + } + + column = &columns[i]; + sw = column->sw_column->data; + + if (flecs_switch_get(sw, first) != column->sw_case) { + first = flecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; + } + } + } + } while (i != count); + + cur->first = iter->sparse_first = first; + cur->count = 1; + + return 0; +done: + /* Iterated all elements in the sparse list, we should move to the + * next matched table. */ + iter->sparse_smallest = 0; + iter->sparse_first = 0; + + return -1; +} + +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) + +static +int bitset_column_next( + ecs_table_t *table, + ecs_vector_t *bitset_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) +{ + /* Precomputed single-bit test */ + static const uint64_t bitmask[64] = { + (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, + (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, + (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, + (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, + (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, + (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, + (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, + (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, + (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, + (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, + (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, + (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, + (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, + (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, + (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, + (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 + }; + + /* Precomputed test to verify if remainder of block is set (or not) */ + static const uint64_t bitmask_remain[64] = { + BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), + BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), + BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), + BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), + BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), + BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), + BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), + BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), + BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), + BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), + BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), + BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), + BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), + BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), + BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), + BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), + BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), + BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), + BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), + BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), + BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), + BS_MAX - (BS_MAX >> 1) + }; + + int32_t i, count = ecs_vector_count(bitset_columns); + flecs_bitset_column_t *columns = ecs_vector_first( + bitset_columns, flecs_bitset_column_t); + int32_t bs_offset = table->bs_column_offset; + + int32_t first = iter->bitset_first; + int32_t last = 0; + + for (i = 0; i < count; i ++) { + flecs_bitset_column_t *column = &columns[i]; + ecs_bs_column_t *bs_column = columns[i].bs_column; + + if (!bs_column) { + ecs_data_t *data = table->data; + int32_t index = column->column_index; + ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); + bs_column = &data->bs_columns[index - bs_offset]; + columns[i].bs_column = bs_column; + } + + ecs_bitset_t *bs = &bs_column->data; + int32_t bs_elem_count = bs->count; + int32_t bs_block = first >> 6; + int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; + + if (bs_block >= bs_block_count) { + goto done; + } + + uint64_t *data = bs->data; + int32_t bs_start = first & 0x3F; + + /* Step 1: find the first non-empty block */ + uint64_t v = data[bs_block]; + uint64_t remain = bitmask_remain[bs_start]; + while (!(v & remain)) { + /* If no elements are remaining, move to next block */ + if ((++bs_block) >= bs_block_count) { + /* No non-empty blocks left */ + goto done; + } + + bs_start = 0; + remain = BS_MAX; /* Test the full block */ + v = data[bs_block]; + } + + /* Step 2: find the first non-empty element in the block */ + while (!(v & bitmask[bs_start])) { + bs_start ++; + + /* Block was not empty, so bs_start must be smaller than 64 */ + ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); + } + + /* Step 3: Find number of contiguous enabled elements after start */ + int32_t bs_end = bs_start, bs_block_end = bs_block; + + remain = bitmask_remain[bs_end]; + while ((v & remain) == remain) { + bs_end = 0; + bs_block_end ++; + + if (bs_block_end == bs_block_count) { + break; + } + + v = data[bs_block_end]; + remain = BS_MAX; /* Test the full block */ + } + + /* Step 4: find remainder of enabled elements in current block */ + if (bs_block_end != bs_block_count) { + while ((v & bitmask[bs_end])) { + bs_end ++; + } + } + + /* Block was not 100% occupied, so bs_start must be smaller than 64 */ + ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); + + /* Step 5: translate to element start/end and make sure that each column + * range is a subset of the previous one. */ + first = bs_block * 64 + bs_start; + int32_t cur_last = bs_block_end * 64 + bs_end; + + /* No enabled elements found in table */ + if (first == cur_last) { + goto done; + } + + /* If multiple bitsets are evaluated, make sure each subsequent range + * is equal or a subset of the previous range */ + if (i) { + /* If the first element of a subsequent bitset is larger than the + * previous last value, start over. */ + if (first >= last) { + i = -1; + continue; + } + + /* Make sure the last element of the range doesn't exceed the last + * element of the previous range. */ + if (cur_last > last) { + cur_last = last; + } + } + + last = cur_last; + int32_t elem_count = last - first; + + /* Make sure last element doesn't exceed total number of elements in + * the table */ + if (elem_count > bs_elem_count) { + elem_count = bs_elem_count; + } + + cur->first = first; + cur->count = elem_count; + iter->bitset_first = first; + } + + /* Keep track of last processed element for iteration */ + iter->bitset_first = last; + + return 0; +done: + return -1; +} + +static +void mark_columns_dirty( + ecs_query_t *query, + ecs_matched_table_t *table_data) +{ + ecs_table_t *table = table_data->table; + + if (table && table->dirty_state) { + ecs_term_t *terms = query->filter.terms; + int32_t c = 0, i, count = query->filter.term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + + if (term->inout != EcsIn && (term->inout != EcsInOutDefault || + (subj->entity == EcsThis && subj->set.mask == EcsSelf))) + { + int32_t table_column = table_data->columns[c]; + if (table_column > 0 && table_column <= table->column_count) { + table->dirty_state[table_column] ++; + } + } + + if (terms[i].oper == EcsOr) { + do { + i ++; + } while ((i < count) && terms[i].oper == EcsOr); + } + + c ++; + } + } +} + +/* Return next table */ +bool ecs_query_next( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_iter_t *iter = &it->iter.query; + ecs_page_iter_t *piter = &iter->page_iter; + ecs_query_t *query = it->query; + ecs_world_t *world = query->world; + (void)world; + + it->is_valid = true; + + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + if (!query->constraints_satisfied) { + goto done; + } + + ecs_table_slice_t *slice = ecs_vector_first( + query->table_slices, ecs_table_slice_t); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + + ecs_assert(!slice || query->order_by, ECS_INTERNAL_ERROR, NULL); + + ecs_page_cursor_t cur; + int32_t table_count = it->table_count; + int32_t prev_count = it->total_count; + + int i; + for (i = iter->index; i < table_count; i ++) { + ecs_matched_table_t *table_data = slice ? slice[i].table : &tables[i]; + ecs_table_t *table = table_data->table; + ecs_data_t *data = NULL; + + iter->index = i + 1; + + if (table) { + ecs_vector_t *bitset_columns = table_data->bitset_columns; + ecs_vector_t *sparse_columns = table_data->sparse_columns; + data = flecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + it->table_columns = data->columns; + + if (slice) { + cur.first = slice[i].start_row; + cur.count = slice[i].count; + } else { + cur.first = 0; + cur.count = ecs_table_count(table); + } + + if (cur.count) { + if (bitset_columns) { + if (bitset_column_next(table, bitset_columns, iter, + &cur) == -1) + { + /* No more enabled components for table */ + continue; + } else { + iter->index = i; + } + } + + if (sparse_columns) { + if (sparse_column_next(table, table_data, + sparse_columns, iter, &cur) == -1) + { + /* No more elements in sparse column */ + continue; + } else { + iter->index = i; + } + } + + int ret = ecs_page_iter_next(piter, &cur); + if (ret < 0) { + goto done; + } else if (ret > 0) { + continue; + } + } else { + continue; + } + + ecs_entity_t *entity_buffer = ecs_vector_first( + data->entities, ecs_entity_t); + it->entities = &entity_buffer[cur.first]; + it->offset = cur.first; + it->count = cur.count; + it->total_count = cur.count; + } + + it->table = table_data->table; + it->ids = table_data->ids; + it->columns = table_data->columns; + it->types = table_data->types; + it->subjects = table_data->subjects; + it->sizes = table_data->sizes; + it->references = table_data->references; + it->frame_offset += prev_count; + + ecs_iter_init(it); + + populate_ptrs(world, it); + + if (query->flags & EcsQueryHasOutColumns) { + if (table) { + mark_columns_dirty(query, table_data); + } + } + + goto yield; + } + +done: + ecs_iter_fini(it); + return false; + +yield: + return true; +} + +bool ecs_query_next_w_filter( + ecs_iter_t *iter, + const ecs_filter_t *filter) +{ + ecs_table_t *table; + + do { + if (!ecs_query_next(iter)) { + return false; + } + table = iter->table; + } while (filter && !flecs_table_match_filter(iter->world, table, filter)); + + return true; +} + +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t current, + int32_t total) +{ + int32_t per_worker, first, prev_offset = it->offset; + + do { + if (!ecs_query_next(it)) { + return false; + } + + int32_t count = it->count; + per_worker = count / total; + first = per_worker * current; + + count -= per_worker * total; + + if (count) { + if (current < count) { + per_worker ++; + first += current; + } else { + first += count; + } + } + + if (!per_worker && !(it->query->flags & EcsQueryNeedsTables)) { + if (current == 0) { + populate_ptrs(it->world, it); + return true; + } else { + return false; + } + } + } while (!per_worker); + + it->frame_offset -= prev_offset; + it->count = per_worker; + it->offset += first; + it->entities = &it->entities[first]; + it->frame_offset += first; + + populate_ptrs(it->world, it); + + return true; +} + +bool ecs_query_changed( + ecs_query_t *query) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + return tables_dirty(query); +} + +bool ecs_query_orphaned( + ecs_query_t *query) +{ + return query->flags & EcsQueryIsOrphaned; +} + + +static +uint64_t ids_hash(const void *ptr) { + const ecs_ids_t *type = ptr; + ecs_id_t *ids = type->array; + int32_t count = type->count; + uint64_t hash = flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); + return hash; +} + +static +int ids_compare(const void *ptr_1, const void *ptr_2) { + const ecs_ids_t *type_1 = ptr_1; + const ecs_ids_t *type_2 = ptr_2; + + 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; + + int32_t i; + for (i = 0; i < count_1; i ++) { + ecs_id_t id_1 = ids_1[i]; + ecs_id_t id_2 = ids_2[i]; + + if (id_1 != id_2) { + return (id_1 > id_2) - (id_1 < id_2); + } + } + + return 0; +} + +ecs_hashmap_t flecs_table_hashmap_new(void) { + return flecs_hashmap_new(ecs_ids_t, ecs_table_t*, ids_hash, ids_compare); +} + +const EcsComponent* flecs_component_from_id( + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_entity_t pair = 0; + + /* If this is a pair, get the pair component from the identifier */ + if (ECS_HAS_ROLE(e, PAIR)) { + pair = e; + e = ecs_get_alive(world, ECS_PAIR_RELATION(e)); + + if (ecs_has_id(world, e, EcsTag)) { + return NULL; + } + } + + if (e & ECS_ROLE_MASK) { + return NULL; + } + + const EcsComponent *component = ecs_get(world, e, EcsComponent); + if ((!component || !component->size) && pair) { + /* If this is a pair column and the pair is not a component, use + * the component type of the component the pair is applied to. */ + e = ECS_PAIR_OBJECT(pair); + + /* Because generations are not stored in the pair, get the currently + * alive id */ + e = ecs_get_alive(world, e); + + /* If a pair is used with a not alive id, the pair is not valid */ + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + + component = ecs_get(world, e, EcsComponent); + } + + return component; +} + +/* Count number of columns with data (excluding tags) */ +static +int32_t data_column_count( + ecs_world_t * world, + ecs_table_t * table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + /* Typically all components will be clustered together at the start of + * the type as components are created from a separate id pool, and type + * vectors are sorted. + * Explicitly check for EcsComponent and EcsName since the ecs_has check + * doesn't work during bootstrap. */ + if ((component == ecs_id(EcsComponent)) || + (component == ecs_pair(ecs_id(EcsIdentifier), EcsName)) || + (component == ecs_pair(ecs_id(EcsIdentifier), EcsSymbol)) || + flecs_component_from_id(world, component) != NULL) + { + count = c_ptr_i + 1; + } + }); + + return count; +} + +/* Ensure the ids used in the columns exist */ +static +int32_t ensure_columns( + ecs_world_t * world, + ecs_table_t * table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(component); + ecs_entity_t obj = ECS_PAIR_OBJECT(component); + ecs_ensure(world, rel); + ecs_ensure(world, obj); + } else if (component & ECS_ROLE_MASK) { + ecs_entity_t e = ECS_PAIR_OBJECT(component); + ecs_ensure(world, e); + } else { + ecs_ensure(world, component); + } + }); + + return count; +} + +/* Count number of switch columns */ +static +int32_t switch_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, SWITCH)) { + if (!count) { + table->sw_column_offset = c_ptr_i; + } + count ++; + } + }); + + return count; +} + +/* Count number of bitset columns */ +static +int32_t bitset_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, DISABLED)) { + if (!count) { + table->bs_column_offset = c_ptr_i; + } + count ++; + } + }); + + return count; +} + +static +ecs_type_t entities_to_type( + ecs_ids_t *entities) +{ + if (entities->count) { + ecs_vector_t *result = NULL; + ecs_vector_set_count(&result, ecs_entity_t, entities->count); + ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); + ecs_os_memcpy(array, entities->array, ECS_SIZEOF(ecs_entity_t) * entities->count); + return result; + } else { + return NULL; + } +} + +static +ecs_edge_t* get_edge( + ecs_table_t *node, + ecs_entity_t e) +{ + if (e < ECS_HI_COMPONENT_ID) { + if (!node->lo_edges) { + node->lo_edges = ecs_os_calloc_n(ecs_edge_t, ECS_HI_COMPONENT_ID); + } + return &node->lo_edges[e]; + } else { + if (!node->hi_edges) { + node->hi_edges = ecs_map_new(ecs_edge_t, 1); + } + return ecs_map_ensure(node->hi_edges, ecs_edge_t, e); + } +} + +static +void init_edges( + ecs_table_t * table) +{ + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t count = ecs_vector_count(table->type); + + table->lo_edges = NULL; + table->hi_edges = NULL; + + /* Iterate components for table, initialize edges that point to self */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + ecs_edge_t *edge = get_edge(table, id); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + edge->add = table; + } +} + +static +void init_flags( + ecs_world_t * world, + ecs_table_t * table) +{ + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t count = ecs_vector_count(table->type); + + /* Iterate components to initialize table flags */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + /* As we're iterating over the table components, also set the table + * flags. These allow us to quickly determine if the table contains + * data that needs to be handled in a special way, like prefabs or + * containers */ + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; + } + + if (id == EcsModule) { + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } + + if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + table->flags |= EcsTableIsDisabled; + } + + /* If table contains disabled entities, mark it as disabled */ + if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } + + /* Does table have exclusive or columns */ + if (ECS_HAS_ROLE(id, XOR)) { + table->flags |= EcsTableHasXor; + } + + /* Does table have IsA relations */ + if (ECS_HAS_RELATION(id, EcsIsA)) { + table->flags |= EcsTableHasIsA; + } + + /* Does table have switch columns */ + if (ECS_HAS_ROLE(id, SWITCH)) { + table->flags |= EcsTableHasSwitch; + } + + /* Does table support component disabling */ + if (ECS_HAS_ROLE(id, DISABLED)) { + table->flags |= EcsTableHasDisabled; + } + + /* Does table have ChildOf relations */ + if (ECS_HAS_RELATION(id, EcsChildOf)) { + ecs_entity_t obj = ecs_pair_object(world, id); + 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; + } + } + } +} + +static +void init_table( + ecs_world_t * world, + ecs_table_t * table, + ecs_ids_t * entities) +{ + table->type = entities_to_type(entities); + table->c_info = NULL; + table->data = NULL; + table->flags = 0; + table->dirty_state = NULL; + table->monitors = NULL; + table->on_set = NULL; + table->on_set_all = NULL; + table->on_set_override = NULL; + table->un_set_all = NULL; + table->alloc_count = 0; + table->lock = 0; + + /* Ensure the component ids for the table exist */ + ensure_columns(world, table); + + table->queries = NULL; + table->column_count = data_column_count(world, table); + table->sw_column_count = switch_column_count(table); + table->bs_column_count = bitset_column_count(table); + + init_edges(table); + init_flags(world, table); + + flecs_register_table(world, table); + + /* Register component info flags for all columns */ + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableComponentInfo + }); +} + +static +ecs_table_t *create_table( + ecs_world_t * world, + ecs_ids_t * entities, + flecs_hashmap_result_t table_elem) +{ + ecs_table_t *result = flecs_sparse_add(world->store.tables, ecs_table_t); + result->id = flecs_sparse_last_id(world->store.tables); + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + init_table(world, result, entities); + +#ifndef NDEBUG + char *expr = ecs_type_str(world, result->type); + ecs_trace_2("table #[green][%s]#[normal] created [%p]", expr, result); + ecs_os_free(expr); +#endif + ecs_log_push(); + + /* 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_ids_t key = { + .array = ecs_vector_first(result->type, ecs_id_t), + .count = ecs_vector_count(result->type) + }; + *(ecs_ids_t*)table_elem.key = key; + + flecs_notify_queries(world, &(ecs_query_event_t) { + .kind = EcsQueryTableMatch, + .table = result + }); + + ecs_log_pop(); + + return result; +} + +static +void add_entity_to_type( + ecs_type_t type, + ecs_entity_t add, + ecs_entity_t replace, + ecs_ids_t *out) +{ + int32_t count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + bool added = false; + + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (e == replace) { + continue; + } + + if (e > add && !added) { + out->array[el ++] = add; + added = true; + } + + out->array[el ++] = e; + + ecs_assert(el <= out->count, ECS_INTERNAL_ERROR, NULL); + } + + if (!added) { + out->array[el ++] = add; + } + + out->count = el; + + ecs_assert(out->count != 0, ECS_INTERNAL_ERROR, NULL); +} + +static +void remove_entity_from_type( + ecs_type_t type, + ecs_entity_t remove, + ecs_ids_t *out) +{ + int32_t count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (e != remove) { + out->array[el ++] = e; + ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); + } + } + + out->count = el; +} + +static +void create_backlink_after_add( + ecs_table_t * next, + ecs_table_t * prev, + ecs_entity_t add) +{ + ecs_edge_t *edge = get_edge(next, add); + if (!edge->remove) { + edge->remove = prev; + } +} + +static +void create_backlink_after_remove( + ecs_table_t * next, + ecs_table_t * prev, + ecs_entity_t add) +{ + ecs_edge_t *edge = get_edge(next, add); + if (!edge->add) { + edge->add = prev; + } +} + +static +ecs_entity_t find_xor_replace( + ecs_world_t * world, + ecs_table_t * table, + ecs_type_t type, + ecs_entity_t add) +{ + if (table->flags & EcsTableHasXor) { + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + int32_t i, type_count = ecs_vector_count(type); + ecs_type_t xor_type = NULL; + + for (i = type_count - 1; i >= 0; i --) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, XOR)) { + ecs_entity_t e_type = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e_type, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_has_id( + world, type_ptr->normalized, add, true)) + { + xor_type = type_ptr->normalized; + } + } else if (xor_type) { + if (ecs_type_has_id(world, xor_type, e, true)) { + return e; + } + } + } + } + + return 0; +} + +int32_t flecs_table_switch_from_case( + const ecs_world_t * world, + const ecs_table_t * table, + ecs_entity_t add) +{ + ecs_type_t type = table->type; + ecs_data_t *data = flecs_table_get_data(table); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + int32_t i, count = table->sw_column_count; + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + add = add & ECS_COMPONENT_MASK; + + ecs_sw_column_t *sw_columns = NULL; + + if (data && (sw_columns = data->sw_columns)) { + /* Fast path, we can get the switch type from the column data */ + for (i = 0; i < count; i ++) { + ecs_type_t sw_type = sw_columns[i].type; + if (ecs_type_has_id(world, sw_type, add, true)) { + return i; + } + } + } else { + /* Slow path, table is empty, so we'll have to get the switch types by + * actually inspecting the switch type entities. */ + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i + table->sw_column_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_has_id( + world, type_ptr->normalized, add, true)) + { + return i; + } + } + } + + /* If a table was not found, this is an invalid switch case */ + ecs_abort(ECS_TYPE_INVALID_CASE, NULL); + + return -1; +} + +static +ecs_table_t *find_or_create_table_include( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t add) +{ + /* If table has one or more switches and this is a case, return self */ + if (ECS_HAS_ROLE(add, CASE)) { + ecs_assert((node->flags & EcsTableHasSwitch) != 0, + ECS_TYPE_INVALID_CASE, NULL); + return node; + } else { + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + + ecs_ids_t entities = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * (count + 1)), + .count = count + 1 + }; + + /* If table has a XOR column, check if the entity that is being added to + * the table is part of the XOR type, and if it is, find the current + * entity in the table type matching the XOR type. This entity must be + * replaced in the new table, to ensure the XOR constraint isn't + * violated. */ + ecs_entity_t replace = find_xor_replace(world, node, type, add); + + add_entity_to_type(type, add, replace, &entities); + + ecs_table_t *result = flecs_table_find_or_create(world, &entities); + + if (result != node) { + create_backlink_after_add(result, node, add); + } + + return result; + } +} + +static +ecs_table_t *find_or_create_table_exclude( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t remove) +{ + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + + ecs_ids_t entities = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * count), + .count = count + }; + + remove_entity_from_type(type, remove, &entities); + + ecs_table_t *result = flecs_table_find_or_create(world, &entities); + if (!result) { + return NULL; + } + + if (result != node) { + create_backlink_after_remove(result, node, remove); + } + + return result; +} + +ecs_table_t* flecs_table_traverse_remove( + ecs_world_t * world, + ecs_table_t * node, + ecs_ids_t * to_remove, + ecs_ids_t * removed) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = to_remove->count; + ecs_entity_t *entities = to_remove->array; + node = node ? node : &world->store.root; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Removing 0 from an entity is not valid */ + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_edge_t *edge = get_edge(node, e); + ecs_table_t *next = edge->remove; + + if (!next) { + if (edge->add == node) { + /* Find table with all components of node except 'e' */ + next = find_or_create_table_exclude(world, node, e); + if (!next) { + return NULL; + } + + edge->remove = next; + } else { + /* If the add edge does not point to self, the table + * does not have the entity in to_remove. */ + continue; + } + } + + bool has_case = ECS_HAS_ROLE(e, CASE); + if (removed && (node != next || has_case)) { + removed->array[removed->count ++] = e; + } + + node = next; + } + + return node; +} + +static +void find_owned_components( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t base, + ecs_ids_t * owned) +{ + /* If we're adding an IsA relationship, check if the base + * has OWNED components that need to be added to the instance */ + ecs_type_t t = ecs_get_type(world, base); + + int i, count = ecs_vector_count(t); + ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_RELATION(e, EcsIsA)) { + find_owned_components(world, node, ECS_PAIR_OBJECT(e), owned); + } else + if (ECS_HAS_ROLE(e, OWNED)) { + e = e & ECS_COMPONENT_MASK; + + /* If entity is a type, add each component in the type */ + const EcsType *t_ptr = ecs_get(world, e, EcsType); + if (t_ptr) { + ecs_type_t n = t_ptr->normalized; + int32_t j, n_count = ecs_vector_count(n); + ecs_entity_t *n_entities = ecs_vector_first(n, ecs_entity_t); + for (j = 0; j < n_count; j ++) { + owned->array[owned->count ++] = n_entities[j]; + } + } else { + owned->array[owned->count ++] = ECS_PAIR_OBJECT(e); + } + } + } +} + +ecs_table_t* flecs_table_traverse_add( + ecs_world_t * world, + ecs_table_t * node, + ecs_ids_t * to_add, + ecs_ids_t * added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = to_add->count; + ecs_entity_t *entities = to_add->array; + node = node ? node : &world->store.root; + + ecs_entity_t owned_array[ECS_MAX_ADD_REMOVE]; + ecs_ids_t owned = { + .array = owned_array, + .count = 0 + }; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Adding 0 to an entity is not valid */ + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_edge_t *edge = get_edge(node, e); + ecs_table_t *next = edge->add; + + if (!next) { + next = find_or_create_table_include(world, node, e); + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + edge->add = next; + } + + bool has_case = ECS_HAS_ROLE(e, CASE); + if (added && (node != next || has_case)) { + added->array[added->count ++] = e; + } + + if ((node != next) && ECS_HAS_RELATION(e, EcsIsA)) { + find_owned_components( + world, next, ecs_pair_object(world, e), &owned); + } + + node = next; + } + + /* In case OWNED components were found, add them as well */ + if (owned.count) { + node = flecs_table_traverse_add(world, node, &owned, added); + } + + return node; +} + +static +bool ecs_entity_array_is_ordered( + const ecs_ids_t *entities) +{ + ecs_entity_t prev = 0; + ecs_entity_t *array = entities->array; + int32_t i, count = entities->count; + + for (i = 0; i < count; i ++) { + if (!array[i] && !prev) { + continue; + } + if (array[i] <= prev) { + return false; + } + prev = array[i]; + } + + return true; +} + +static +int32_t ecs_entity_array_dedup( + ecs_entity_t *array, + int32_t count) +{ + int32_t j, k; + ecs_entity_t prev = array[0]; + + for (k = j = 1; k < count; j ++, k++) { + ecs_entity_t e = array[k]; + if (e == prev) { + k ++; + } + + array[j] = e; + prev = e; + } + + return count - (k - j); +} + +#ifndef NDEBUG + +static +int32_t count_occurrences( + ecs_world_t * world, + ecs_ids_t * entities, + ecs_entity_t entity, + int32_t constraint_index) +{ + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, + ECS_INVALID_PARAMETER, "flag must be applied to type"); + + ecs_type_t type = type_ptr->normalized; + int32_t count = 0; + + int i; + for (i = 0; i < constraint_index; i ++) { + ecs_entity_t e = entities->array[i]; + if (e & ECS_ROLE_MASK) { + break; + } + + if (ecs_type_has_id(world, type, e, false)) { + count ++; + } + } + + return count; +} + +static +void verify_constraints( + ecs_world_t * world, + ecs_ids_t * entities) +{ + int i, count = entities->count; + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = entities->array[i]; + ecs_entity_t mask = e & ECS_ROLE_MASK; + if (!mask || + ((mask != ECS_OR) && + (mask != ECS_XOR) && + (mask != ECS_NOT))) + { + break; + } + + ecs_entity_t entity = e & ECS_COMPONENT_MASK; + int32_t matches = count_occurrences(world, entities, entity, i); + switch(mask) { + case ECS_OR: + ecs_assert(matches >= 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + case ECS_XOR: + ecs_assert(matches == 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + case ECS_NOT: + ecs_assert(matches == 0, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + } + } +} + +#endif + +static +ecs_table_t* find_or_create( + ecs_world_t *world, + const ecs_ids_t *ids) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + /* Make sure array is ordered and does not contain duplicates */ + int32_t type_count = ids->count; + ecs_id_t *ordered = NULL; + + if (!type_count) { + return &world->store.root; + } + + if (!ecs_entity_array_is_ordered(ids)) { + ecs_size_t size = ECS_SIZEOF(ecs_entity_t) * type_count; + ordered = ecs_os_alloca(size); + ecs_os_memcpy(ordered, ids->array, size); + qsort(ordered, (size_t)type_count, sizeof(ecs_entity_t), + flecs_entity_compare_qsort); + type_count = ecs_entity_array_dedup(ordered, type_count); + } else { + ordered = ids->array; + } + + ecs_ids_t ordered_ids = { + .array = ordered, + .count = type_count + }; + + ecs_table_t *table; + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + world->store.table_map, &ordered_ids, ecs_table_t*); + if ((table = *(ecs_table_t**)elem.value)) { + 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->is_readonly, ECS_INTERNAL_ERROR, NULL); + +#ifndef NDEBUG + /* Check for constraint violations */ + verify_constraints(world, &ordered_ids); +#endif + + /* If we get here, the table has not been found, so create it. */ + ecs_table_t *result = create_table(world, &ordered_ids, elem); + + ecs_assert(ordered_ids.count == ecs_vector_count(result->type), + ECS_INTERNAL_ERROR, NULL); + + return result; +} + +ecs_table_t* flecs_table_find_or_create( + ecs_world_t * world, + const ecs_ids_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + return find_or_create(world, components); +} + +ecs_table_t* ecs_table_from_type( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_ids_t components = flecs_type_to_ids(type); + return flecs_table_find_or_create( + world, &components); +} + +void flecs_init_root_table( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_ids_t entities = { + .array = NULL, + .count = 0 + }; + + init_table(world, &world->store.root, &entities); +} + +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + uint32_t i; + + if (table->lo_edges) { + for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) { + ecs_edge_t *e = &table->lo_edges[i]; + ecs_table_t *add = e->add, *remove = e->remove; + + if (add) { + add->lo_edges[i].remove = NULL; + } + if (remove) { + remove->lo_edges[i].add = NULL; + } + } + } + + ecs_map_iter_t it = ecs_map_iter(table->hi_edges); + ecs_edge_t *edge; + ecs_map_key_t component; + while ((edge = ecs_map_next(&it, ecs_edge_t, &component))) { + ecs_table_t *add = edge->add, *remove = edge->remove; + if (add) { + ecs_edge_t *e = get_edge(add, component); + e->remove = NULL; + if (!e->add) { + ecs_map_remove(add->hi_edges, component); + } + } + if (remove) { + ecs_edge_t *e = get_edge(remove, component); + e->add = NULL; + if (!e->remove) { + ecs_map_remove(remove->hi_edges, component); + } + } + } +} + +/* 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_ids_t arr = { .array = &id, .count = 1 }; + return flecs_table_traverse_add(world, table, &arr, NULL); +} + +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_ids_t arr = { .array = &id, .count = 1 }; + return flecs_table_traverse_remove(world, table, &arr, NULL); +} + +/* The ratio used to determine whether the map should rehash. If + * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define LOAD_FACTOR (1.5f) +#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) +#define GET_ELEM(array, elem_size, index) \ + ECS_OFFSET(array, (elem_size) * (index)) + +typedef struct ecs_bucket_t { + ecs_map_key_t *keys; /* Array with keys */ + void *payload; /* Payload array */ + int32_t count; /* Number of elements in bucket */ +} ecs_bucket_t; + +struct ecs_map_t { + ecs_bucket_t *buckets; + int32_t elem_size; + int32_t bucket_count; + int32_t count; +}; + +/* Get bucket count for number of elements */ +static +int32_t get_bucket_count( + int32_t element_count) +{ + return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); +} + +/* Get bucket index for provided map key */ +static +int32_t get_bucket_id( + int32_t bucket_count, + ecs_map_key_t key) +{ + ecs_assert(bucket_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t result = (int32_t)(key & ((uint64_t)bucket_count - 1)); + ecs_assert(result < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return result; +} + +/* Get bucket for key */ +static +ecs_bucket_t* get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) +{ + int32_t bucket_count = map->bucket_count; + if (!bucket_count) { + return NULL; + } + + int32_t bucket_id = get_bucket_id(bucket_count, key); + ecs_assert(bucket_id < bucket_count, ECS_INTERNAL_ERROR, NULL); + + return &map->buckets[bucket_id]; +} + +/* Ensure that map has at least new_count buckets */ +static +void ensure_buckets( + ecs_map_t *map, + int32_t new_count) +{ + int32_t bucket_count = map->bucket_count; + new_count = flecs_next_pow_of_2(new_count); + if (new_count && new_count > bucket_count) { + map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); + map->bucket_count = new_count; + + ecs_os_memset( + ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), + 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); + } +} + +/* Free contents of bucket */ +static +void clear_bucket( + ecs_bucket_t *bucket) +{ + ecs_os_free(bucket->keys); + ecs_os_free(bucket->payload); + bucket->keys = NULL; + bucket->payload = NULL; + bucket->count = 0; +} + +/* Clear all buckets */ +static +void clear_buckets( + ecs_map_t *map) +{ + ecs_bucket_t *buckets = map->buckets; + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + clear_bucket(&buckets[i]); + } + ecs_os_free(buckets); + map->buckets = NULL; + map->bucket_count = 0; +} + +/* Find or create bucket for specified key */ +static +ecs_bucket_t* ensure_bucket( + ecs_map_t *map, + ecs_map_key_t key) +{ + if (!map->bucket_count) { + ensure_buckets(map, 2); + } + + int32_t bucket_id = get_bucket_id(map->bucket_count, key); + ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} + +/* Add element to bucket */ +static +int32_t add_to_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + int32_t index = bucket->count ++; + int32_t bucket_count = index + 1; + + bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); + bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); + bucket->keys[index] = key; + + if (payload) { + void *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_os_memcpy(elem, payload, elem_size); + } + + return index; +} + +/* Remove element from bucket */ +static +void remove_from_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + int32_t index) +{ + (void)key; + + ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_count = -- bucket->count; + + if (index != bucket->count) { + ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); + bucket->keys[index] = bucket->keys[bucket_count]; + + ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); + + ecs_os_memcpy(elem, last_elem, elem_size); + } +} + +/* Get payload pointer for key from bucket */ +static +void* get_from_bucket( + ecs_bucket_t *bucket, + ecs_map_key_t key, + ecs_size_t elem_size) +{ + ecs_map_key_t *keys = bucket->keys; + int32_t i, count = bucket->count; + + for (i = 0; i < count; i ++) { + if (keys[i] == key) { + return GET_ELEM(bucket->payload, elem_size, i); + } + } + return NULL; +} + +/* Grow number of buckets */ +static +void rehash( + ecs_map_t *map, + int32_t bucket_count) +{ + ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); + + ecs_size_t elem_size = map->elem_size; + + ensure_buckets(map, bucket_count); + + ecs_bucket_t *buckets = map->buckets; + ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_id; + + /* Iterate backwards as elements could otherwise be moved to existing + * buckets which could temporarily cause the number of elements in a + * bucket to exceed BUCKET_COUNT. */ + for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { + ecs_bucket_t *bucket = &buckets[bucket_id]; + + int i, count = bucket->count; + ecs_map_key_t *key_array = bucket->keys; + void *payload_array = bucket->payload; + + for (i = 0; i < count; i ++) { + ecs_map_key_t key = key_array[i]; + void *elem = GET_ELEM(payload_array, elem_size, i); + int32_t new_bucket_id = get_bucket_id(bucket_count, key); + + if (new_bucket_id != bucket_id) { + ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; + + add_to_bucket(new_bucket, elem_size, key, elem); + remove_from_bucket(bucket, elem_size, key, i); + + count --; + i --; + } + } + + if (!bucket->count) { + clear_bucket(bucket); + } + } +} + +ecs_map_t* _ecs_map_new( + ecs_size_t elem_size, + ecs_size_t alignment, + int32_t element_count) +{ + (void)alignment; + + ecs_map_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_map_t) * 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + int32_t bucket_count = get_bucket_count(element_count); + + result->count = 0; + result->elem_size = elem_size; + + ensure_buckets(result, bucket_count); + + return result; +} + +void ecs_map_free( + ecs_map_t *map) +{ + if (map) { + clear_buckets(map); + ecs_os_free(map); + } +} + +void* _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + (void)elem_size; + + if (!map) { + return NULL; + } + + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return NULL; + } + + return get_from_bucket(bucket, key, elem_size); +} + +void* _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key) +{ + void * ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); + + if (ptr_ptr) { + return *(void**)ptr_ptr; + } else { + return NULL; + } +} + +bool ecs_map_has( + const ecs_map_t *map, + ecs_map_key_t key) +{ + if (!map) { + return false; + } + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return false; + } + + return get_from_bucket(bucket, key, 0) != NULL; +} + +void * _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + void *result = _ecs_map_get(map, elem_size, key); + if (!result) { + result = _ecs_map_set(map, elem_size, key, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(result, 0, elem_size); + } + + return result; +} + +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = ensure_bucket(map, key); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + + void *elem = get_from_bucket(bucket, key, elem_size); + if (!elem) { + int32_t index = add_to_bucket(bucket, elem_size, key, payload); + + int32_t map_count = ++map->count; + int32_t target_bucket_count = get_bucket_count(map_count); + int32_t map_bucket_count = map->bucket_count; + + if (target_bucket_count > map_bucket_count) { + rehash(map, target_bucket_count); + bucket = ensure_bucket(map, key); + return get_from_bucket(bucket, key, elem_size); + } else { + return GET_ELEM(bucket->payload, elem_size, index); + } + } else { + if (payload) { + ecs_os_memcpy(elem, payload, elem_size); + } + return elem; + } +} + +void ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return; + } + + int32_t i, bucket_count = bucket->count; + for (i = 0; i < bucket_count; i ++) { + if (bucket->keys[i] == key) { + remove_from_bucket(bucket, map->elem_size, key, i); + map->count --; + } + } +} + +int32_t ecs_map_count( + const ecs_map_t *map) +{ + return map ? map->count : 0; +} + +int32_t ecs_map_bucket_count( + const ecs_map_t *map) +{ + return map ? map->bucket_count : 0; +} + +void ecs_map_clear( + ecs_map_t *map) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + clear_buckets(map); + map->count = 0; +} + +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .bucket_index = 0, + .element_index = 0 + }; +} + +void* _ecs_map_next( + ecs_map_iter_t *iter, + ecs_size_t elem_size, + ecs_map_key_t *key_out) +{ + const ecs_map_t *map = iter->map; + if (!map) { + return NULL; + } + + ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = iter->bucket; + int32_t element_index = iter->element_index; + elem_size = map->elem_size; + + do { + if (!bucket) { + int32_t bucket_index = iter->bucket_index; + ecs_bucket_t *buckets = map->buckets; + if (bucket_index < map->bucket_count) { + bucket = &buckets[bucket_index]; + iter->bucket = bucket; + + element_index = 0; + iter->element_index = 0; + } else { + return NULL; + } + } + + if (element_index < bucket->count) { + iter->element_index = element_index + 1; + break; + } else { + bucket = NULL; + iter->bucket_index ++; + } + } while (true); + + if (key_out) { + *key_out = bucket->keys[element_index]; + } + + return GET_ELEM(bucket->payload, elem_size, element_index); +} + +void* _ecs_map_next_ptr( + ecs_map_iter_t *iter, + ecs_map_key_t *key_out) +{ + void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); + if (result) { + return *(void**)result; + } else { + return NULL; + } +} + +void ecs_map_grow( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t target_count = map->count + element_count; + int32_t bucket_count = get_bucket_count(target_count); + + if (bucket_count > map->bucket_count) { + rehash(map, bucket_count); + } +} + +void ecs_map_set_size( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_count = get_bucket_count(element_count); + + if (bucket_count) { + rehash(map, bucket_count); + } +} + +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + + if (used) { + *used = map->count * map->elem_size; + } + + if (allocd) { + *allocd += ECS_SIZEOF(ecs_map_t); + + int i, bucket_count = map->bucket_count; + for (i = 0; i < bucket_count; i ++) { + ecs_bucket_t *bucket = &map->buckets[i]; + *allocd += KEY_SIZE * bucket->count; + *allocd += map->elem_size * bucket->count; + } + + *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; + } +} + +#define INIT_CACHE(it, f, term_count)\ + if (!it->f && term_count) {\ + if (term_count < ECS_TERM_CACHE_SIZE) {\ + it->f = it->cache.f;\ + it->cache.f##_alloc = false;\ + } else {\ + it->f = ecs_os_malloc(ECS_SIZEOF(*(it->f)) * term_count);\ + it->cache.f##_alloc = true;\ + }\ + } + +#define FINI_CACHE(it, f)\ + if (it->f) {\ + if (it->cache.f##_alloc) {\ + ecs_os_free((void*)it->f);\ + }\ + } + +void ecs_iter_init( + ecs_iter_t *it) +{ + INIT_CACHE(it, ids, it->column_count); + INIT_CACHE(it, types, it->column_count); + INIT_CACHE(it, columns, it->column_count); + INIT_CACHE(it, subjects, it->column_count); + INIT_CACHE(it, sizes, it->column_count); + INIT_CACHE(it, ptrs, it->column_count); + + it->is_valid = true; +} + +void ecs_iter_fini( + ecs_iter_t *it) +{ + ecs_assert(it->is_valid == true, ECS_INVALID_PARAMETER, NULL); + it->is_valid = false; + + FINI_CACHE(it, ids); + FINI_CACHE(it, types); + FINI_CACHE(it, columns); + FINI_CACHE(it, subjects); + FINI_CACHE(it, sizes); + FINI_CACHE(it, ptrs); +} + +/* --- Public API --- */ + +void* ecs_term_w_size( + const ecs_iter_t *it, + size_t size, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || ecs_term_size(it, term) == size, + ECS_INVALID_PARAMETER, NULL); + + (void)size; + + if (!term) { + return it->entities; + } + + if (!it->ptrs) { + return NULL; + } + + return it->ptrs[term - 1]; +} + +bool ecs_term_is_owned( + const ecs_iter_t *it, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term > 0, ECS_INVALID_PARAMETER, NULL); + return it->subjects == NULL || it->subjects[term - 1] == 0; +} + +bool ecs_term_is_readonly( + const ecs_iter_t *it, + int32_t term_index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term_index > 0, ECS_INVALID_PARAMETER, NULL); + + ecs_query_t *query = it->query; + + /* If this is not a query iterator, readonly is meaningless */ + ecs_assert(query != NULL, ECS_INVALID_OPERATION, NULL); + (void)query; + + ecs_term_t *term = &it->query->filter.terms[term_index - 1]; + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + + if (term->inout == EcsIn) { + return true; + } else { + ecs_term_id_t *subj = &term->args[0]; + + if (term->inout == EcsInOutDefault) { + if (subj->entity != EcsThis) { + return true; + } + + if ((subj->set.mask != EcsSelf) && + (subj->set.mask != EcsDefaultSet)) + { + return true; + } + } + } + + return false; +} + +bool ecs_term_is_set( + const ecs_iter_t *it, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(term > 0, ECS_INVALID_PARAMETER, NULL); + + return it->columns[term - 1] != 0; +} + +ecs_entity_t ecs_term_source( + const ecs_iter_t *it, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term > 0, ECS_INVALID_PARAMETER, NULL); + + if (!it->subjects) { + return 0; + } else { + return it->subjects[term - 1]; + } +} + +ecs_id_t ecs_term_id( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->ids != NULL, ECS_INTERNAL_ERROR, NULL); + return it->ids[index - 1]; +} + +size_t ecs_term_size( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + + if (index == 0) { + return sizeof(ecs_entity_t); + } + + return flecs_to_size_t(it->sizes[index - 1]); +} + +ecs_table_t* ecs_iter_table( + const ecs_iter_t *it) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + return it->table; +} + +ecs_type_t ecs_iter_type( + const ecs_iter_t *it) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + + /* If no table is set it means that the iterator isn't pointing to anything + * yet. The most likely cause for this is that the operation is invoked on + * a new iterator for which "next" hasn't been invoked yet, or on an + * iterator that is out of elements. */ + ecs_table_t *table = ecs_iter_table(it); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + return table->type; +} + +int32_t ecs_iter_find_column( + const ecs_iter_t *it, + ecs_entity_t component) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_type_index_of(it->table->type, 0, component); +} + +void* ecs_iter_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column_index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + (void)size; + + ecs_table_t *table = it->table; + ecs_assert(column_index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + + if (table->column_count <= column_index) { + return NULL; + } + + ecs_column_t *columns = it->table_columns; + ecs_column_t *column = &columns[column_index]; + ecs_assert(!size || (ecs_size_t)size == column->size, ECS_INVALID_PARAMETER, NULL); + + return ecs_vector_first_t(column->data, column->size, column->alignment); +} + +size_t ecs_iter_column_size( + const ecs_iter_t *it, + int32_t column_index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = it->table; + ecs_assert(column_index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + + if (table->column_count <= column_index) { + return 0; + } + + ecs_column_t *columns = it->table_columns; + ecs_column_t *column = &columns[column_index]; + + return flecs_to_size_t(column->size); +} + +static +int32_t count_events( + const ecs_entity_t *events) +{ + int32_t i; + + for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { + if (!events[i]) { + break; + } + } + + return i; +} + +static +void register_id_trigger( + ecs_map_t *set, + ecs_trigger_t *trigger) +{ + ecs_trigger_t **t = ecs_map_ensure(set, ecs_trigger_t*, trigger->id); + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + *t = trigger; +} + +static +ecs_map_t* unregister_id_trigger( + ecs_map_t *set, + ecs_trigger_t *trigger) +{ + ecs_map_remove(set, trigger->id); + + if (!ecs_map_count(set)) { + ecs_map_free(set); + return NULL; + } + + return set; +} + +static +void register_trigger( + ecs_world_t *world, + ecs_trigger_t *trigger) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *triggers = world->id_triggers; + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_trigger_t *idt = ecs_map_ensure(triggers, + ecs_id_trigger_t, trigger->term.id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int i; + for (i = 0; i < trigger->event_count; i ++) { + ecs_map_t **set = NULL; + if (trigger->events[i] == EcsOnAdd) { + set = &idt->on_add_triggers; + } else if (trigger->events[i] == EcsOnRemove) { + set = &idt->on_remove_triggers; + } else if (trigger->events[i] == EcsOnSet) { + set = &idt->on_set_triggers; + } else if (trigger->events[i] == EcsUnSet) { + set = &idt->un_set_triggers; + } else { + /* Invalid event provided */ + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + ecs_assert(set != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!*set) { + *set = ecs_map_new(ecs_trigger_t*, 1); + + // First trigger of its kind, send table notification + flecs_notify_tables(world, trigger->term.id, &(ecs_table_event_t){ + .kind = EcsTableTriggerMatch, + .event = trigger->events[i] + }); + } + + register_id_trigger(*set, trigger); + } +} + +static +void unregister_trigger( + ecs_world_t *world, + ecs_trigger_t *trigger) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *triggers = world->id_triggers; + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_trigger_t *idt = ecs_map_get( + triggers, ecs_id_trigger_t, trigger->term.id); + if (!idt) { + return; + } + + int i; + for (i = 0; i < trigger->event_count; i ++) { + ecs_map_t **set = NULL; + if (trigger->events[i] == EcsOnAdd) { + set = &idt->on_add_triggers; + } else if (trigger->events[i] == EcsOnRemove) { + set = &idt->on_remove_triggers; + } else if (trigger->events[i] == EcsOnSet) { + set = &idt->on_set_triggers; + } else if (trigger->events[i] == EcsUnSet) { + set = &idt->un_set_triggers; + } else { + /* Invalid event provided */ + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + if (!*set) { + return; + } + + *set = unregister_id_trigger(*set, trigger); + } + + ecs_map_remove(triggers, trigger->id); +} + +ecs_map_t* flecs_triggers_get( + const ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *triggers = world->id_triggers; + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_trigger_t *idt = ecs_map_get(triggers, ecs_id_trigger_t, id); + if (!idt) { + return NULL; + } + + ecs_map_t *set = NULL; + + if (event == EcsOnAdd) { + set = idt->on_add_triggers; + } else if (event == EcsOnRemove) { + set = idt->on_remove_triggers; + } else if (event == EcsOnSet) { + set = idt->on_set_triggers; + } else if (event == EcsUnSet) { + set = idt->un_set_triggers; + } + + if (ecs_map_count(set)) { + return set; + } else { + return NULL; + } +} + +static +void notify_trigger_set( + ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t event, + const ecs_map_t *triggers, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count) +{ + if (!triggers) { + return; + } + + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); + entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row); + + int32_t index = ecs_type_index_of(table->type, 0, id); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + index ++; + + ecs_entity_t ids[1] = { id }; + int32_t columns[1] = { index }; + ecs_size_t sizes[1] = { 0 }; + + /* If there is no data, ensure that system won't try to get it */ + if (table->column_count < index) { + columns[0] = 0; + } else { + ecs_column_t *column = &data->columns[index - 1]; + if (!column->size) { + columns[0] = 0; + } + } + + void *ptr = NULL; + + if (columns[0] && data && data->columns) { + ecs_column_t *col = &data->columns[index - 1]; + ptr = ecs_vector_get_t(col->data, col->size, col->alignment, row); + sizes[0] = col->size; + } + + ecs_type_t types[1] = { ecs_type_from_id(world, id) }; + + ecs_iter_t it = { + .world = world, + .event = event, + .event_id = id, + .table = table, + .columns = columns, + .ids = ids, + .types = types, + .sizes = sizes, + .ptrs = &ptr, + .table_count = 1, + .inactive_table_count = 0, + .column_count = 1, + .table_columns = data->columns, + .entities = entities, + .offset = row, + .count = count, + .is_valid = true + }; + + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + it.system = t->entity; + it.self = t->self; + it.ctx = t->ctx; + it.binding_ctx = t->binding_ctx; + it.term_index = t->term.index; + t->action(&it); + } +} + +void flecs_triggers_notify( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count) +{ + notify_trigger_set(world, id, event, + flecs_triggers_get(world, id, event), + table, data, row, count); + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t pred = ECS_PAIR_RELATION(id); + ecs_entity_t obj = ECS_PAIR_OBJECT(id); + + notify_trigger_set(world, id, event, + flecs_triggers_get(world, ecs_pair(pred, EcsWildcard), event), + table, data, row, count); + + notify_trigger_set(world, id, event, + flecs_triggers_get(world, ecs_pair(EcsWildcard, obj), event), + table, data, row, count); + + notify_trigger_set(world, id, event, + flecs_triggers_get(world, ecs_pair(EcsWildcard, EcsWildcard), event), + table, data, row, count); + } else { + notify_trigger_set(world, id, event, + flecs_triggers_get(world, EcsWildcard, event), + table, data, row, count); + } +} + +ecs_entity_t ecs_trigger_init( + ecs_world_t *world, + const ecs_trigger_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + char *name = NULL; + const char *expr = desc->expr; + + /* If entity is provided, create it */ + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t entity = ecs_entity_init(world, &desc->entity); + + bool added = false; + EcsTrigger *comp = ecs_get_mut(world, entity, EcsTrigger, &added); + if (added) { + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Something went wrong with the construction of the entity */ + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + name = ecs_get_fullpath(world, entity); + + ecs_term_t term; + if (expr) { + #ifdef FLECS_PARSER + const char *ptr = ecs_parse_term(world, name, expr, expr, &term); + if (!ptr) { + goto error; + } + + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error( + name, expr, 0, "invalid empty trigger expression"); + goto error; + } + + if (ptr[0]) { + ecs_parser_error(name, expr, 0, + "too many terms in trigger expression (expected 1)"); + goto error; + } + #else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + #endif + } else { + term = ecs_term_copy(&desc->term); + } + + if (ecs_term_finalize(world, name, expr, &term)) { + goto error; + } + + /* Currently triggers are not supported for specific entities */ + ecs_assert(term.args[0].entity == EcsThis, ECS_UNSUPPORTED, NULL); + + ecs_trigger_t *trigger = flecs_sparse_add(world->triggers, ecs_trigger_t); + trigger->id = flecs_sparse_last_id(world->triggers); + trigger->term = ecs_term_move(&term); + trigger->action = desc->callback; + trigger->ctx = desc->ctx; + trigger->binding_ctx = desc->binding_ctx; + trigger->ctx_free = desc->ctx_free; + trigger->binding_ctx_free = desc->binding_ctx_free; + trigger->event_count = count_events(desc->events); + ecs_os_memcpy(trigger->events, desc->events, + trigger->event_count * ECS_SIZEOF(ecs_entity_t)); + trigger->entity = entity; + trigger->self = desc->self; + + comp->trigger = trigger; + + /* Trigger must have at least one event */ + ecs_assert(trigger->event_count != 0, ECS_INVALID_PARAMETER, NULL); + + register_trigger(world, trigger); + + ecs_term_fini(&term); + } else { + ecs_assert(comp->trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If existing entity handle was provided, override existing params */ + if (existing) { + if (desc->callback) { + ((ecs_trigger_t*)comp->trigger)->action = desc->callback; + } + if (desc->ctx) { + ((ecs_trigger_t*)comp->trigger)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ((ecs_trigger_t*)comp->trigger)->binding_ctx = desc->binding_ctx; + } + } + } + + ecs_os_free(name); + return entity; +error: + ecs_os_free(name); + return 0; +} + +void* ecs_get_trigger_ctx( + const ecs_world_t *world, + ecs_entity_t trigger) +{ + const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); + if (t) { + return t->trigger->ctx; + } else { + return NULL; + } +} + +void* ecs_get_trigger_binding_ctx( + const ecs_world_t *world, + ecs_entity_t trigger) +{ + const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); + if (t) { + return t->trigger->binding_ctx; + } else { + return NULL; + } +} + +void flecs_trigger_fini( + ecs_world_t *world, + ecs_trigger_t *trigger) +{ + unregister_trigger(world, trigger); + ecs_term_fini(&trigger->term); + + if (trigger->ctx_free) { + trigger->ctx_free(trigger->ctx); + } + + if (trigger->binding_ctx_free) { + trigger->binding_ctx_free(trigger->binding_ctx); + } + + flecs_sparse_remove(world->triggers, trigger->id); +} + +int8_t flflecs_to_i8( + int64_t v) +{ + ecs_assert(v < INT8_MAX, ECS_INTERNAL_ERROR, NULL); + return (int8_t)v; +} + +int16_t flecs_to_i16( + int64_t v) +{ + ecs_assert(v < INT16_MAX, ECS_INTERNAL_ERROR, NULL); + return (int16_t)v; +} + +uint32_t flecs_to_u32( + uint64_t v) +{ + ecs_assert(v < UINT32_MAX, ECS_INTERNAL_ERROR, NULL); + return (uint32_t)v; +} + +size_t flecs_to_size_t( + int64_t size) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); + return (size_t)size; +} + +ecs_size_t flecs_from_size_t( + size_t size) +{ + ecs_assert(size < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return (ecs_size_t)size; +} + +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_entity_compare_qsort( + const void *e1, + const void *e2) +{ + ecs_entity_t v1 = *(ecs_entity_t*)e1; + ecs_entity_t v2 = *(ecs_entity_t*)e2; + return flecs_entity_compare(v1, NULL, v2, NULL); +} + +uint64_t flecs_string_hash( + const void *ptr) +{ + const ecs_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} + +/* + This code was taken from sokol_time.h + + zlib/libpng license + Copyright (c) 2018 Andre Weissflog + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. +*/ + + +static int ecs_os_time_initialized; + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +static double _ecs_os_time_win_freq; +static LARGE_INTEGER _ecs_os_time_win_start; +#elif defined(__APPLE__) && defined(__MACH__) +#include <mach/mach_time.h> +static mach_timebase_info_data_t _ecs_os_time_osx_timebase; +static uint64_t _ecs_os_time_osx_start; +#else /* anything else, this will need more care for non-Linux platforms */ +#include <time.h> +static uint64_t _ecs_os_time_posix_start; +#endif + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +int64_t 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 + +void flecs_os_time_setup(void) { + if ( ecs_os_time_initialized) { + return; + } + + ecs_os_time_initialized = 1; + #if defined(_WIN32) + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&_ecs_os_time_win_start); + _ecs_os_time_win_freq = (double)freq.QuadPart / 1000000000.0; + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_ecs_os_time_osx_timebase); + _ecs_os_time_osx_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _ecs_os_time_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +uint64_t flecs_os_time_now(void) { + ecs_assert(ecs_os_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t now; + + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)(qpc_t.QuadPart / _ecs_os_time_win_freq); + #elif defined(__APPLE__) && defined(__MACH__) + now = (uint64_t) int64_muldiv((int64_t)mach_absolute_time(), (int64_t)_ecs_os_time_osx_timebase.numer, (int64_t)_ecs_os_time_osx_timebase.denom); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec); + #endif + + return now; +} + +void flecs_os_time_sleep( + int32_t sec, + int32_t nanosec) +{ +#ifndef _WIN32 + 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_os_err("nanosleep failed"); + } +#else + 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); +#endif +} + + +#if defined(_WIN32) + +static ULONG win32_current_resolution; + +void flecs_increase_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); + if (!hntdll) { + return; + } + + LONG (__stdcall *pNtSetTimerResolution)( + ULONG desired, BOOLEAN set, ULONG * current); + + pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) + GetProcAddress(hntdll, "NtSetTimerResolution"); + + if(!pNtSetTimerResolution) { + return; + } + + ULONG current, resolution = 10000; /* 1 ms */ + + if (!enable && win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + win32_current_resolution = 0; + return; + } else if (!enable) { + return; + } + + if (resolution == win32_current_resolution) { + return; + } + + if (win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + } + + if (pNtSetTimerResolution(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(pNtSetTimerResolution(resolution, 1, ¤t)) return; + } + + win32_current_resolution = resolution; +} + +#else +void flecs_increase_timer_resolution(bool enable) +{ + (void)enable; + return; +} +#endif + +#ifdef FLECS_PIPELINE + + +/* Worker thread */ +static +void* worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; + + /* Start worker thread, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; + + if (!world->quit_workers) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } + + ecs_os_mutex_unlock(world->sync_mutex); + + while (!world->quit_workers) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + + ecs_pipeline_run( + (ecs_world_t*)stage, + world->pipeline, + world->stats.delta_time); + + ecs_set_scope((ecs_world_t*)stage, old_scope); + } + + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); + + return NULL; +} + +/* Start threads */ +static +void start_workers( + ecs_world_t *world, + int32_t threads) +{ + ecs_set_stages(world, threads); + + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + + int32_t i; + for (i = 0; i < threads; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(stage->magic == ECS_STAGE_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_get(world->worker_stages, ecs_stage_t, i); + stage->thread = ecs_os_thread_new(worker, stage); + ecs_assert(stage->thread != 0, ECS_THREAD_ERROR, NULL); + } +} + +/* Wait until all workers are running */ +static +void wait_for_workers( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + bool wait = true; + + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == stage_count) { + wait = false; + } + ecs_os_mutex_unlock(world->sync_mutex); + } while (wait); +} + +/* Synchronize worker threads */ +static +void sync_worker( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + + /* Signal that thread is waiting */ + ecs_os_mutex_lock(world->sync_mutex); + if (++ world->workers_waiting == stage_count) { + /* Only signal main thread when all threads are waiting */ + ecs_os_cond_signal(world->sync_cond); + } + + /* Wait until main thread signals that thread can continue */ + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Wait until all threads are waiting on sync point */ +static +void wait_for_sync( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_waiting != stage_count) { + ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + } + + /* We should have been signalled unless all workers are waiting on sync */ + ecs_assert(world->workers_waiting == stage_count, + ECS_INTERNAL_ERROR, NULL); + + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Signal workers that they can start/resume work */ +static +void signal_workers( + ecs_world_t *world) +{ + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/** Stop worker threads */ +static +bool ecs_stop_threads( + ecs_world_t *world) +{ + 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. */ + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + if (stage->thread) { + threads_active = true; + break; + } + stage->thread = 0; + }); + + /* If no threads are active, just return */ + if (!threads_active) { + return false; + } + + /* Make sure all threads are running, to ensure they catch the signal */ + wait_for_workers(world); + + /* Signal threads should quit */ + world->quit_workers = true; + signal_workers(world); + + /* Join all threads with main */ + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + ecs_os_thread_join(stage->thread); + stage->thread = 0; + }); + + world->quit_workers = false; + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); + + /* Deinitialize stages */ + ecs_set_stages(world, 0); + + return true; +} + +/* -- Private functions -- */ + +void ecs_worker_begin( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + if (stage_count == 1) { + ecs_staging_begin(world); + } +} + +bool ecs_worker_sync( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + + int32_t build_count = world->stats.pipeline_build_count_total; + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + /* If there are no threads, merge in place */ + if (stage_count == 1) { + ecs_staging_end(world); + ecs_pipeline_update(world, world->pipeline, false); + ecs_staging_begin(world); + + /* Synchronize all workers. The last worker to reach the sync point will + * signal the main thread, which will perform the merge. */ + } else { + sync_worker(world); + } + + return world->stats.pipeline_build_count_total != build_count; +} + +void ecs_worker_end( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + /* If there are no threads, merge in place */ + if (stage_count == 1) { + ecs_staging_end(world); + + /* Synchronize all workers. The last worker to reach the sync point will + * signal the main thread, which will perform the merge. */ + } else { + sync_worker(world); + } +} + +void ecs_workers_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + int32_t stage_count = ecs_get_stage_count(world); + + ecs_time_t start = {0}; + if (world->measure_frame_time) { + ecs_time_measure(&start); + } + + if (stage_count == 1) { + ecs_pipeline_update(world, pipeline, true); + ecs_entity_t old_scope = ecs_set_scope(world, 0); + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_pipeline_run(stage, pipeline, delta_time); + ecs_set_scope(world, old_scope); + } else { + int32_t i, sync_count = ecs_pipeline_update(world, pipeline, true); + + /* Make sure workers are running and ready */ + wait_for_workers(world); + + /* Synchronize n times for each op in the pipeline */ + for (i = 0; i < sync_count; i ++) { + ecs_staging_begin(world); + + /* Signal workers that they should start running systems */ + world->workers_waiting = 0; + signal_workers(world); + + /* Wait until all workers are waiting on sync point */ + wait_for_sync(world); + + /* Merge */ + ecs_staging_end(world); + + int32_t update_count; + if ((update_count = ecs_pipeline_update(world, pipeline, false))) { + /* The number of operations in the pipeline could have changed + * as result of the merge */ + sync_count = update_count; + } + } + } + + if (world->measure_frame_time) { + world->stats.system_time_total += (FLECS_FLOAT)ecs_time_measure(&start); + } +} + + +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + + int32_t stage_count = ecs_get_stage_count(world); + + if (!world->arg_threads && stage_count != threads) { + /* Stop existing threads */ + if (stage_count > 1) { + if (ecs_stop_threads(world)) { + ecs_os_cond_free(world->worker_cond); + ecs_os_cond_free(world->sync_cond); + ecs_os_mutex_free(world->sync_mutex); + } + } + + /* Start threads if number of threads > 1 */ + if (threads > 1) { + world->worker_cond = ecs_os_cond_new(); + world->sync_cond = ecs_os_cond_new(); + world->sync_mutex = ecs_os_mutex_new(); + start_workers(world, threads); + } + } +} + +#endif + +#ifdef FLECS_PIPELINE + + +ECS_TYPE_DECL(EcsPipelineQuery); + +static ECS_CTOR(EcsPipelineQuery, ptr, { + memset(ptr, 0, _size); +}) + +static ECS_DTOR(EcsPipelineQuery, ptr, { + ecs_vector_free(ptr->ops); +}) + +static +int compare_entity( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); +} + +static +int group_by_phase( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t pipeline, + void *ctx) +{ + (void)ctx; + + const EcsType *pipeline_type = ecs_get(world, pipeline, EcsType); + ecs_assert(pipeline_type != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Find tag in system that belongs to pipeline */ + ecs_entity_t *sys_comps = ecs_vector_first(type, ecs_entity_t); + int32_t c, t, count = ecs_vector_count(type); + + ecs_entity_t *tags = ecs_vector_first(pipeline_type->normalized, ecs_entity_t); + int32_t tag_count = ecs_vector_count(pipeline_type->normalized); + + ecs_entity_t result = 0; + + for (c = 0; c < count; c ++) { + ecs_entity_t comp = sys_comps[c]; + for (t = 0; t < tag_count; t ++) { + if (comp == tags[t]) { + result = comp; + break; + } + } + if (result) { + break; + } + } + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL); + + return (int)result; +} + +typedef enum ComponentWriteState { + NotWritten = 0, + WriteToMain, + WriteToStage +} ComponentWriteState; + +typedef struct write_state_t { + ecs_map_t *components; + bool wildcard; +} write_state_t; + +static +int32_t get_write_state( + ecs_map_t *write_state, + ecs_entity_t component) +{ + int32_t *ptr = ecs_map_get(write_state, int32_t, component); + if (ptr) { + return *ptr; + } else { + return 0; + } +} + +static +void set_write_state( + write_state_t *write_state, + ecs_entity_t component, + int32_t value) +{ + if (component == EcsWildcard) { + ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); + write_state->wildcard = true; + } else { + ecs_map_set(write_state->components, component, &value); + } +} + +static +void reset_write_state( + write_state_t *write_state) +{ + ecs_map_clear(write_state->components); + write_state->wildcard = false; +} + +static +int32_t get_any_write_state( + write_state_t *write_state) +{ + if (write_state->wildcard) { + return WriteToStage; + } + + ecs_map_iter_t it = ecs_map_iter(write_state->components); + int32_t *elem; + while ((elem = ecs_map_next(&it, int32_t, NULL))) { + if (*elem == WriteToStage) { + return WriteToStage; + } + } + + return 0; +} + +static +bool check_term_component( + ecs_term_t *term, + bool is_active, + ecs_entity_t component, + write_state_t *write_state) +{ + int32_t state = get_write_state(write_state->components, component); + + ecs_term_id_t *subj = &term->args[0]; + + if ((subj->set.mask & EcsSelf) && subj->entity == EcsThis && term->oper != EcsNot) { + switch(term->inout) { + case EcsInOutDefault: + case EcsInOut: + case EcsIn: + if (state == WriteToStage || write_state->wildcard) { + return true; + } + // fall through + case EcsOut: + if (is_active && term->inout != EcsIn) { + set_write_state(write_state, component, WriteToMain); + } + }; + } else if (!subj->entity || term->oper == EcsNot) { + bool needs_merge = false; + + switch(term->inout) { + case EcsInOutDefault: + case EcsIn: + case EcsInOut: + if (state == WriteToStage) { + needs_merge = true; + } + if (component == EcsWildcard) { + if (get_any_write_state(write_state) == WriteToStage) { + needs_merge = true; + } + } + break; + default: + break; + }; + + switch(term->inout) { + case EcsInOutDefault: + if (!(subj->set.mask & EcsSelf) || !subj->entity || + subj->entity != EcsThis) + { + break; + } + // fall through + case EcsInOut: + case EcsOut: + if (is_active) { + set_write_state(write_state, component, WriteToStage); + } + break; + default: + break; + }; + + if (needs_merge) { + return true; + } + } + + return false; +} + +static +bool check_term( + ecs_term_t *term, + bool is_active, + write_state_t *write_state) +{ + if (term->oper != EcsOr) { + return check_term_component( + term, is_active, term->id, write_state); + } + + return false; +} + +static +bool build_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + EcsPipelineQuery *pq) +{ + (void)pipeline; + + ecs_query_iter(pq->query); + + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + return false; + } + + ecs_trace_2("rebuilding pipeline #[green]%s", + ecs_get_name(world, pipeline)); + + world->stats.pipeline_build_count_total ++; + + write_state_t ws = { + .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), + .wildcard = false + }; + + ecs_pipeline_op_t *op = NULL; + ecs_vector_t *ops = NULL; + ecs_query_t *query = pq->build_query; + + if (pq->ops) { + ecs_vector_free(pq->ops); + } + + /* Iterate systems in pipeline, add ops for running / merging */ + ecs_iter_t it = ecs_query_iter(query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + + int i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *q = sys[i].query; + if (!q) { + continue; + } + + bool needs_merge = false; + bool is_active = !ecs_has_id( + world, it.entities[i], EcsInactive); + + ecs_term_t *terms = q->filter.terms; + int32_t t, term_count = q->filter.term_count; + for (t = 0; t < term_count; t ++) { + needs_merge |= check_term(&terms[t], is_active, &ws); + } + + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + reset_write_state(&ws); + 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) { + for (t = 0; t < term_count; t ++) { + needs_merge |= check_term(&terms[t], 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_vector_add(&ops, ecs_pipeline_op_t); + op->count = 0; + } + + /* Don't increase count for inactive systems, as they are ignored by + * the query used to run the pipeline. */ + if (is_active) { + op->count ++; + } + } + } + + ecs_map_free(ws.components); + + /* Force sort of query as this could increase the match_count */ + pq->match_count = pq->query->match_count; + pq->ops = ops; + + return true; +} + +static +int32_t iter_reset( + const EcsPipelineQuery *pq, + ecs_iter_t *iter_out, + ecs_pipeline_op_t **op_out, + ecs_entity_t move_to) +{ + ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; + + *iter_out = ecs_query_iter(pq->query); + while (ecs_query_next(iter_out)) { + int32_t i; + for(i = 0; i < iter_out->count; i ++) { + ecs_entity_t e = iter_out->entities[i]; + + ran_since_merge ++; + if (ran_since_merge == op->count) { + ran_since_merge = 0; + op ++; + } + + if (e == move_to) { + *op_out = op; + return i; + } + } + } + + ecs_abort(ECS_UNSUPPORTED, NULL); + + return -1; +} + +int32_t ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); + + /* 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) { + flecs_eval_component_monitors(world); + } + + bool added = false; + EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, &added); + ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + build_pipeline(world, pipeline, pq); + + return ecs_vector_count(pq->ops); +} + +void ecs_pipeline_run( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); + + if (!pipeline) { + pipeline = world->pipeline; + } + + /* If the world is passed to ecs_pipeline_run, the function will take care + * of staging, so the world should not be in staged mode when called. */ + if (world->magic == ECS_WORLD_MAGIC) { + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + + /* Forward to worker_progress. This function handles staging, threading + * and synchronization across workers. */ + ecs_workers_progress(world, pipeline, delta_time); + return; + + /* If a stage is passed, the function could be ran from a worker thread. In + * that case the main thread should manage staging, and staging should be + * enabled. */ + } else { + ecs_assert(world->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + } + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; + + int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); + int32_t stage_count = ecs_get_stage_count(world); + + ecs_worker_begin(stage->thread_ctx); + + ecs_iter_t it = ecs_query_iter(pq->query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + + int32_t i; + for(i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + + ecs_run_intern(world, stage, e, &sys[i], stage_index, stage_count, + delta_time, 0, 0, NULL, NULL); + + ran_since_merge ++; + world->stats.systems_ran_frame ++; + + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + + /* If the set of matched systems changed as a result of the + * merge, we have to reset the iterator and move it to our + * current position (system). If there are a lot of systems + * in the pipeline this can be an expensive operation, but + * should happen infrequently. */ + if (ecs_worker_sync(stage->thread_ctx)) { + i = iter_reset(pq, &it, &op, e); + op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); + sys = ecs_term(&it, EcsSystem, 1); + } + } + } + } + + ecs_worker_end(stage->thread_ctx); +} + +static +void add_pipeline_tags_to_sig( + ecs_world_t *world, + ecs_term_t *terms, + ecs_type_t type) +{ + (void)world; + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + for (i = 0; i < count; i ++) { + terms[i] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsOr, + .pred.entity = entities[i], + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + } +} + +static +ecs_query_t* build_pipeline_query( + ecs_world_t *world, + ecs_entity_t pipeline, + const char *name, + bool with_inactive) +{ + const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t type_count = ecs_vector_count(type_ptr->normalized); + int32_t term_count = 2; + + if (with_inactive) { + term_count ++; + } + + ecs_term_t *terms = ecs_os_malloc( + (type_count + term_count) * ECS_SIZEOF(ecs_term_t)); + + terms[0] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsAnd, + .pred.entity = ecs_id(EcsSystem), + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + + terms[1] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsNot, + .pred.entity = EcsDisabledIntern, + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + + if (with_inactive) { + terms[2] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsNot, + .pred.entity = EcsInactive, + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + } + + add_pipeline_tags_to_sig(world, &terms[term_count], type_ptr->normalized); + + ecs_query_t *result = ecs_query_init(world, &(ecs_query_desc_t){ + .filter = { + .name = name, + .terms_buffer = terms, + .terms_buffer_count = term_count + type_count + }, + .order_by = compare_entity, + .group_by = group_by_phase, + .group_by_id = pipeline + }); + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_os_free(terms); + + return result; +} + +static +void EcsOnUpdatePipeline( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; + + int32_t i; + for (i = it->count - 1; i >= 0; i --) { + ecs_entity_t pipeline = entities[i]; + +#ifndef NDEBUG + ecs_trace_1("pipeline #[green]%s#[normal] created", + ecs_get_name(world, pipeline)); +#endif + ecs_log_push(); + + /* Build signature for pipeline quey that matches EcsSystems, has the + * pipeline phases as OR columns, and ignores systems with EcsInactive + * and EcsDisabledIntern. Note that EcsDisabled is automatically ignored + * by the regular query matching */ + ecs_query_t *query = build_pipeline_query( + world, pipeline, "BuiltinPipelineQuery", true); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Build signature for pipeline build query. The build query includes + * systems that are inactive, as an inactive system may become active as + * a result of another system, and as a result the correct merge + * operations need to be put in place. */ + ecs_query_t *build_query = build_pipeline_query( + world, pipeline, "BuiltinPipelineBuildQuery", false); + ecs_assert(build_query != NULL, ECS_INTERNAL_ERROR, NULL); + + bool added = false; + EcsPipelineQuery *pq = ecs_get_mut( + world, pipeline, EcsPipelineQuery, &added); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + if (added) { + /* Should not modify pipeline after it has been used */ + ecs_assert(pq->ops == NULL, ECS_INVALID_OPERATION, NULL); + + if (pq->query) { + ecs_query_fini(pq->query); + } + if (pq->build_query) { + ecs_query_fini(pq->build_query); + } + } + + pq->query = query; + pq->build_query = build_query; + pq->match_count = -1; + pq->ops = NULL; + + ecs_log_pop(); + } +} + +/* -- Public API -- */ + +bool ecs_progress( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + float delta_time = ecs_frame_begin(world, user_delta_time); + + ecs_pipeline_run(world, 0, delta_time); + + ecs_frame_end(world); + + return !world->should_quit; +} + +void ecs_set_time_scale( + ecs_world_t *world, + FLECS_FLOAT scale) +{ + world->stats.time_scale = scale; +} + +void ecs_reset_clock( + ecs_world_t *world) +{ + world->stats.world_time_total = 0; + world->stats.world_time_total_raw = 0; +} + +void ecs_deactivate_systems( + ecs_world_t *world) +{ + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t pipeline = world->pipeline; + const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Iterate over all systems, add EcsInvalid tag if queries aren't matched + * with any tables */ + ecs_iter_t it = ecs_query_iter(pq->build_query); + + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_none(world, &world->stage); + + while( ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *query = sys[i].query; + if (query) { + if (!ecs_vector_count(query->tables)) { + ecs_add_id(world, it.entities[i], EcsInactive); + } + } + } + } + + flecs_defer_flush(world, &world->stage); +} + +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert( ecs_get(world, pipeline, EcsPipelineQuery) != NULL, + ECS_INVALID_PARAMETER, NULL); + + world->pipeline = pipeline; +} + +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +} + +/* -- Module implementation -- */ + +static +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) +{ + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); + } +} + +void FlecsPipelineImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsPipeline); + + ECS_IMPORT(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_tag(world, EcsPipeline); + flecs_bootstrap_component(world, EcsPipelineQuery); + + /* Phases of the builtin pipeline are regular entities. Names are set so + * they can be resolved by type expressions. */ + flecs_bootstrap_tag(world, EcsPreFrame); + flecs_bootstrap_tag(world, EcsOnLoad); + flecs_bootstrap_tag(world, EcsPostLoad); + flecs_bootstrap_tag(world, EcsPreUpdate); + flecs_bootstrap_tag(world, EcsOnUpdate); + flecs_bootstrap_tag(world, EcsOnValidate); + flecs_bootstrap_tag(world, EcsPostUpdate); + flecs_bootstrap_tag(world, EcsPreStore); + flecs_bootstrap_tag(world, EcsOnStore); + flecs_bootstrap_tag(world, EcsPostFrame); + + ECS_TYPE_IMPL(EcsPipelineQuery); + + /* Set ctor and dtor for PipelineQuery */ + ecs_set(world, ecs_id(EcsPipelineQuery), EcsComponentLifecycle, { + .ctor = ecs_ctor(EcsPipelineQuery), + .dtor = ecs_dtor(EcsPipelineQuery) + }); + + /* When the Pipeline tag is added a pipeline will be created */ + ECS_SYSTEM(world, EcsOnUpdatePipeline, EcsOnSet, Pipeline, Type); + + /* Create the builtin pipeline */ + world->pipeline = ecs_type_init(world, &(ecs_type_desc_t){ + .entity = { + .name = "BuiltinPipeline", + .add = {EcsPipeline} + }, + .ids = { + EcsPreFrame, EcsOnLoad, EcsPostLoad, EcsPreUpdate, EcsOnUpdate, + EcsOnValidate, EcsPostUpdate, EcsPreStore, EcsOnStore, EcsPostFrame + } + }); + + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} + +#endif + +#ifdef FLECS_TIMER + + +ecs_type_t ecs_type(EcsTimer); +ecs_type_t ecs_type(EcsRateFilter); + +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_term(it, EcsTimer, 1); + EcsTickSource *tick_source = ecs_term(it, EcsTickSource, 2); + + 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); + FLECS_FLOAT time_elapsed = timer[i].time + info->delta_time_raw; + FLECS_FLOAT timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + FLECS_FLOAT t = time_elapsed - timeout; + if (t > timeout) { + t = 0; + } + + timer[i].time = t; /* Initialize with remainder */ + tick_source[i].tick = true; + tick_source[i].time_elapsed = time_elapsed; + + 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_term(it, EcsRateFilter, 1); + EcsTickSource *tick_dst = ecs_term(it, EcsTickSource, 2); + + 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_term(it, EcsTickSource, 1); + + /* 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, + FLECS_FLOAT timeout) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } + + return timer; +} + +FLECS_FLOAT ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(timer != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } else { + return 0; + } +} + +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT interval) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = interval, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } + + return timer; +} + +FLECS_FLOAT ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + return 0; + } + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } else { + return 0; + } +} + +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ptr->active = true; + ptr->time = 0; +} + +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ptr->active = false; +} + +ecs_entity_t ecs_set_rate( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + filter = ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); + + EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = filter; + } + + return filter; +} + +/* Deprecated */ +ecs_entity_t ecs_set_rate_filter( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + return ecs_set_rate(world, filter, rate, source); +} + +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + + EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + system_data->tick_source = tick_source; +} + +void FlecsTimerImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsTimer); + + ECS_IMPORT(world, FlecsPipeline); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsTimer); + flecs_bootstrap_component(world, EcsRateFilter); + + /* Add EcsTickSource to timers and rate filters */ + ECS_SYSTEM(world, AddTickSource, EcsPreFrame, [in] Timer || RateFilter, [out] !flecs.system.TickSource); + + /* Timer handling */ + ECS_SYSTEM(world, ProgressTimers, EcsPreFrame, Timer, flecs.system.TickSource); + + /* Rate filter handling */ + ECS_SYSTEM(world, ProgressRateFilters, EcsPreFrame, [in] RateFilter, [out] flecs.system.TickSource); + + /* TickSource without a timer or rate filter just increases each frame */ + ECS_SYSTEM(world, ProgressTickSource, EcsPreFrame, [out] flecs.system.TickSource, !RateFilter, !Timer); +} + +#endif + +#ifdef FLECS_SYSTEM + + +/* Global type variables */ +ECS_TYPE_DECL(EcsComponentLifecycle); +ECS_TYPE_DECL(EcsSystem); +ECS_TYPE_DECL(EcsTickSource); + +static +ecs_on_demand_in_t* get_in_component( + ecs_map_t *component_map, + ecs_entity_t component) +{ + ecs_on_demand_in_t *in = ecs_map_get( + component_map, ecs_on_demand_in_t, component); + if (!in) { + ecs_on_demand_in_t in_value = {0}; + ecs_map_set(component_map, component, &in_value); + in = ecs_map_get(component_map, ecs_on_demand_in_t, component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + } + + return in; +} + +static +void activate_in_columns( + ecs_world_t *world, + ecs_query_t *query, + ecs_map_t *component_map, + bool activate) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + if (terms[i].inout == EcsIn) { + ecs_on_demand_in_t *in = get_in_component( + component_map, terms[i].id); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + + in->count += activate ? 1 : -1; + + ecs_assert(in->count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* If this is the first system that registers the in component, walk + * over all already registered systems to enable them */ + if (in->systems && + ((activate && in->count == 1) || + (!activate && !in->count))) + { + ecs_on_demand_out_t **out = ecs_vector_first( + in->systems, ecs_on_demand_out_t*); + int32_t s, in_count = ecs_vector_count(in->systems); + + for (s = 0; s < in_count; s ++) { + /* Increase the count of the system with the out params */ + out[s]->count += activate ? 1 : -1; + + /* If this is the first out column that is requested from + * the OnDemand system, enable it */ + if (activate && out[s]->count == 1) { + ecs_remove_id(world, out[s]->system, EcsDisabledIntern); + } else if (!activate && !out[s]->count) { + ecs_add_id(world, out[s]->system, EcsDisabledIntern); + } + } + } + } + } +} + +static +void register_out_column( + ecs_map_t *component_map, + ecs_entity_t component, + ecs_on_demand_out_t *on_demand_out) +{ + ecs_on_demand_in_t *in = get_in_component(component_map, component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + + on_demand_out->count += in->count; + ecs_on_demand_out_t **elem = ecs_vector_add(&in->systems, ecs_on_demand_out_t*); + *elem = on_demand_out; +} + +static +void register_out_columns( + ecs_world_t *world, + ecs_entity_t system, + EcsSystem *system_data) +{ + ecs_query_t *query = system_data->query; + ecs_term_t *terms = query->filter.terms; + int32_t out_count = 0, i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + if (terms[i].inout == EcsOut) { + if (!system_data->on_demand) { + system_data->on_demand = ecs_os_malloc(sizeof(ecs_on_demand_out_t)); + ecs_assert(system_data->on_demand != NULL, ECS_OUT_OF_MEMORY, NULL); + + system_data->on_demand->system = system; + system_data->on_demand->count = 0; + } + + /* If column operator is NOT and the inout kind is [out], the system + * explicitly states that it will create the component (it is not + * there, yet it is an out column). In this case it doesn't make + * sense to wait until [in] terms get activated (matched with + * entities) since the component is not there yet. Therefore add it + * to the on_enable_components list, so this system will be enabled + * when a [in] column is enabled, rather than activated */ + ecs_map_t *component_map; + if (terms[i].oper == EcsNot) { + component_map = world->on_enable_components; + } else { + component_map = world->on_activate_components; + } + + register_out_column( + component_map, terms[i].id, + system_data->on_demand); + + out_count ++; + } + } + + /* If there are no out terms in the on-demand system, the system will + * never be enabled */ + ecs_assert(out_count != 0, ECS_NO_OUT_COLUMNS, ecs_get_name(world, system)); +} + +static +void invoke_status_action( + ecs_world_t *world, + ecs_entity_t system, + const EcsSystem *system_data, + ecs_system_status_t status) +{ + ecs_system_status_action_t action = system_data->status_action; + if (action) { + action(world, system, status, system_data->status_ctx); + } +} + +/* Invoked when system becomes active or inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data) +{ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + if (activate) { + /* If activating system, ensure that it doesn't have the Inactive tag. + * Systems are implicitly activated so they are kept out of the main + * loop as long as they aren't used. They are not implicitly deactivated + * to prevent overhead in case of oscillating app behavior. + * After activation, systems that aren't matched with anything can be + * deactivated again by explicitly calling ecs_deactivate_systems. + */ + ecs_remove_id(world, system, EcsInactive); + } + + if (!system_data) { + system_data = ecs_get(world, system, EcsSystem); + } + if (!system_data || !system_data->query) { + return; + } + + if (!activate) { + if (ecs_has_id(world, system, EcsDisabled) || + ecs_has_id(world, system, EcsDisabledIntern)) + { + if (!ecs_vector_count(system_data->query->tables)) { + /* If deactivating a disabled system that isn't matched with + * any active tables, there is nothing to deactivate. */ + return; + } + } + } + + /* If system contains in columns, signal that they are now in use */ + activate_in_columns( + world, system_data->query, world->on_activate_components, activate); + + /* Invoke system status action */ + invoke_status_action(world, system, system_data, + activate ? EcsSystemActivated : EcsSystemDeactivated); + + ecs_trace_2("system #[green]%s#[reset] %s", + ecs_get_name(world, system), + activate ? "activated" : "deactivated"); +} + +/* Actually enable or disable system */ +static +void ecs_enable_system( + ecs_world_t *world, + ecs_entity_t system, + EcsSystem *system_data, + bool enabled) +{ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + ecs_query_t *query = system_data->query; + if (!query) { + return; + } + + if (ecs_vector_count(query->tables)) { + /* Only (de)activate system if it has non-empty tables. */ + ecs_system_activate(world, system, enabled, system_data); + system_data = ecs_get_mut(world, system, EcsSystem, NULL); + } + + /* Enable/disable systems that trigger on [in] enablement */ + activate_in_columns( + world, + query, + world->on_enable_components, + enabled); + + /* Invoke action for enable/disable status */ + invoke_status_action( + world, system, system_data, + enabled ? EcsSystemEnabled : EcsSystemDisabled); +} + +/* -- Public API -- */ + +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + const EcsType *type_ptr = ecs_get( world, entity, EcsType); + if (type_ptr) { + /* If entity is a type, disable all entities in the type */ + ecs_vector_each(type_ptr->normalized, ecs_entity_t, e, { + ecs_enable(world, *e, enabled); + }); + } else { + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); + } + } +} + +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param) +{ + FLECS_FLOAT time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; + + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; + } + + if (tick_source) { + const EcsTickSource *tick = ecs_get( + world, tick_source, EcsTickSource); + + if (tick) { + time_elapsed = tick->time_elapsed; + + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } + } else { + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; + } + } + + ecs_time_t time_start; + bool measure_time = world->measure_system_time; + if (measure_time) { + ecs_os_get_time(&time_start); + } + + ecs_defer_begin(stage->thread_ctx); + + /* Prepare the query iterator */ + ecs_iter_t it = ecs_query_iter_page(system_data->query, offset, limit); + it.world = stage->thread_ctx; + it.system = system; + it.self = system_data->self; + it.delta_time = delta_time; + it.delta_system_time = time_elapsed; + it.world_time = world->stats.world_time_total; + it.frame_offset = offset; + it.param = param; + it.ctx = system_data->ctx; + it.binding_ctx = system_data->binding_ctx; + + ecs_iter_action_t action = system_data->action; + + /* If no filter is provided, just iterate tables & invoke action */ + if (stage_count <= 1) { + while (ecs_query_next_w_filter(&it, filter)) { + action(&it); + } + } else { + while (ecs_query_next_worker(&it, stage_current, stage_count)) { + action(&it); + } + } + + ecs_defer_end(stage->thread_ctx); + + if (measure_time) { + system_data->time_spent += (FLECS_FLOAT)ecs_time_measure(&time_start); + } + + system_data->invoke_count ++; + + return it.interrupted_by; +} + +/* -- Public API -- */ + +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); + + return ecs_run_intern( + world, stage, system, system_data, 0, 0, delta_time, offset, limit, + filter, param); +} + +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); + + return ecs_run_intern( + world, stage, system, system_data, stage_current, stage_count, + delta_time, 0, 0, NULL, param); +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + void *param) +{ + return ecs_run_w_filter(world, system, delta_time, 0, 0, NULL, param); +} + +void flecs_run_monitor( + ecs_world_t *world, + ecs_matched_query_t *monitor, + ecs_ids_t *components, + int32_t row, + int32_t count, + ecs_entity_t *entities) +{ + ecs_query_t *query = monitor->query; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t system = query->system; + const EcsSystem *system_data = ecs_get(world, system, EcsSystem); + ecs_assert(system_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!system_data->action) { + return; + } + + ecs_iter_t it = {0}; + flecs_query_set_iter( world, query, &it, + monitor->matched_table_index, row, count); + + it.world = world; + it.triggered_by = components; + it.is_valid = true; + it.ctx = system_data->ctx; + it.binding_ctx = system_data->binding_ctx; + + if (entities) { + it.entities = entities; + } + + it.system = system; + system_data->action(&it); +} + +ecs_query_t* ecs_get_system_query( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsQuery *q = ecs_get(world, system, EcsQuery); + if (q) { + return q->query; + } else { + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->query; + } else { + return NULL; + } + } +} + +void* ecs_get_system_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->ctx; + } else { + return NULL; + } +} + +void* ecs_get_system_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->binding_ctx; + } else { + return NULL; + } +} + +/* Generic constructor to initialize a component to 0 */ +static +void sys_ctor_init_zero( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entities; + (void)ctx; + memset(ptr, 0, size * (size_t)count); +} + +/* System destructor */ +static +void ecs_colsystem_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)component; + (void)ctx; + (void)size; + + EcsSystem *system_data = ptr; + + int i; + for (i = 0; i < count; i ++) { + EcsSystem *system = &system_data[i]; + ecs_entity_t e = entities[i]; + + /* Invoke Deactivated action for active systems */ + if (system->query && ecs_vector_count(system->query->tables)) { + invoke_status_action(world, e, ptr, EcsSystemDeactivated); + } + + /* Invoke Disabled action for enabled systems */ + if (!ecs_has_id(world, e, EcsDisabled) && + !ecs_has_id(world, e, EcsDisabledIntern)) + { + invoke_status_action(world, e, ptr, EcsSystemDisabled); + } + + ecs_os_free(system->on_demand); + + if (system->ctx_free) { + system->ctx_free(system->ctx); + } + + if (system->status_ctx_free) { + system->status_ctx_free(system->status_ctx); + } + + if (system->binding_ctx_free) { + system->binding_ctx_free(system->binding_ctx); + } + + if (system->query) { + ecs_query_fini(system->query); + } + } +} + +/* Disable system when EcsDisabled is added */ +static +void DisableSystem( + ecs_iter_t *it) +{ + EcsSystem *system_data = ecs_term(it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_enable_system( + it->world, it->entities[i], &system_data[i], false); + } +} + +/* Enable system when EcsDisabled is removed */ +static +void EnableSystem( + ecs_iter_t *it) +{ + EcsSystem *system_data = ecs_term(it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_enable_system( + it->world, it->entities[i], &system_data[i], true); + } +} + +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t result = ecs_entity_init(world, &desc->entity); + if (!result) { + return 0; + } + + bool added = false; + EcsSystem *system = ecs_get_mut(world, result, EcsSystem, &added); + if (added) { + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + memset(system, 0, sizeof(EcsSystem)); + + ecs_query_desc_t query_desc = desc->query; + query_desc.filter.name = desc->entity.name; + query_desc.system = result; + + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, result); + return 0; + } + + /* Re-obtain pointer, as query may have added components */ + system = ecs_get_mut(world, result, EcsSystem, &added); + ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); + + /* Prevent the system from moving while we're initializing */ + ecs_defer_begin(world); + + system->entity = result; + system->query = query; + + system->action = desc->callback; + system->status_action = desc->status_callback; + + system->self = desc->self; + system->ctx = desc->ctx; + system->status_ctx = desc->status_ctx; + system->binding_ctx = desc->binding_ctx; + + system->ctx_free = desc->ctx_free; + system->status_ctx_free = desc->status_ctx_free; + system->binding_ctx_free = desc->binding_ctx_free; + + system->tick_source = desc->tick_source; + + /* If tables have been matched with this system it is active, and we + * should activate the in terms, if any. This will ensure that any + * OnDemand systems get enabled. */ + if (ecs_vector_count(query->tables)) { + ecs_system_activate(world, result, true, system); + } else { + /* If system isn't matched with any tables, mark it as inactive. This + * causes it to be ignored by the main loop. When the system matches + * with a table it will be activated. */ + ecs_add_id(world, result, EcsInactive); + } + + /* If system is enabled, trigger enable components */ + activate_in_columns(world, query, world->on_enable_components, true); + + /* If the query has a OnDemand system tag, register its [out] terms */ + if (ecs_has_id(world, result, EcsOnDemand)) { + register_out_columns(world, result, system); + ecs_assert(system->on_demand != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If there are no systems currently interested in any of the [out] + * terms of the on demand system, disable it */ + if (!system->on_demand->count) { + ecs_add_id(world, result, EcsDisabledIntern); + } + } + + if (!ecs_has_id(world, result, EcsDisabled)) { + /* If system is already enabled, generate enable status. The API + * should guarantee that it exactly matches enable-disable + * notifications and activate-deactivate notifications. */ + invoke_status_action(world, result, system, EcsSystemEnabled); + + /* If column system has active (non-empty) tables, also generate the + * activate status. */ + if (ecs_vector_count(system->query->tables)) { + invoke_status_action(world, result, system, EcsSystemActivated); + } + } + + if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { +#ifdef FLECS_TIMER + if (desc->interval != 0) { + ecs_set_interval(world, result, desc->interval); + } + + if (desc->rate) { + ecs_set_rate(world, result, desc->rate, desc->tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, result, desc->tick_source); + } +#else + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif + } + + ecs_modified(world, result, EcsSystem); + + ecs_trace_1("system #[green]%s#[reset] created with #[red]%s", + ecs_get_name(world, result), query->filter.expr); + + ecs_defer_end(world); + } else { + const char *expr_desc = desc->query.filter.expr; + const char *expr_sys = system->query->filter.expr; + + /* Only check expression if it's set */ + if (expr_desc) { + if (expr_sys && !strcmp(expr_sys, "0")) expr_sys = NULL; + if (expr_desc && !strcmp(expr_desc, "0")) expr_desc = NULL; + + if (expr_sys && expr_desc) { + if (strcmp(expr_sys, expr_desc)) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } else { + if (expr_sys != expr_desc) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } + + /* If expr_desc is not set, and this is an existing system, don't throw + * an error because we could be updating existing parameters of the + * system such as the context or system callback. However, if no + * entity handle was provided, we have to assume that the application is + * trying to redeclare the system. */ + } else if (!existing) { + if (expr_sys) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } + + /* Override the existing callback or context */ + if (desc->callback) { + system->action = desc->callback; + } + if (desc->ctx) { + system->ctx = desc->ctx; + } + if (desc->binding_ctx) { + system->binding_ctx = desc->binding_ctx; + } + } + + return result; +} + +void FlecsSystemImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); + + flecs_bootstrap_tag(world, EcsOnAdd); + flecs_bootstrap_tag(world, EcsOnRemove); + flecs_bootstrap_tag(world, EcsOnSet); + flecs_bootstrap_tag(world, EcsUnSet); + + /* Put following tags in flecs.core so they can be looked up + * without using the flecs.systems prefix. */ + ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore); + flecs_bootstrap_tag(world, EcsDisabledIntern); + flecs_bootstrap_tag(world, EcsInactive); + flecs_bootstrap_tag(world, EcsOnDemand); + flecs_bootstrap_tag(world, EcsMonitor); + ecs_set_scope(world, old_scope); + + ECS_TYPE_IMPL(EcsComponentLifecycle); + ECS_TYPE_IMPL(EcsSystem); + ECS_TYPE_IMPL(EcsTickSource); + + /* Bootstrap ctor and dtor for EcsSystem */ + ecs_set_component_actions_w_id(world, ecs_id(EcsSystem), + &(EcsComponentLifecycle) { + .ctor = sys_ctor_init_zero, + .dtor = ecs_colsystem_dtor + }); + + /* Monitors that trigger when a system is enabled or disabled */ + ECS_SYSTEM(world, DisableSystem, EcsMonitor, + System, Disabled || DisabledIntern, SYSTEM:Hidden); + + ECS_SYSTEM(world, EnableSystem, EcsMonitor, + System, !Disabled, !DisabledIntern, SYSTEM:Hidden); +} + +#endif + +static +ecs_vector_t* sort_and_dedup( + ecs_vector_t *result) +{ + /* Sort vector */ + ecs_vector_sort(result, ecs_id_t, flecs_entity_compare_qsort); + + /* Ensure vector doesn't contain duplicates */ + ecs_id_t *ids = ecs_vector_first(result, ecs_id_t); + int32_t i, offset = 0, count = ecs_vector_count(result); + + for (i = 0; i < count; i ++) { + if (i && ids[i] == ids[i - 1]) { + offset ++; + } + + if (i + offset >= count) { + break; + } + + ids[i] = ids[i + offset]; + } + + ecs_vector_set_count(&result, ecs_id_t, i - offset); + + return result; +} + +/** Parse callback that adds type to type identifier */ +static +ecs_vector_t* expr_to_ids( + ecs_world_t *world, + const char *name, + const char *expr) +{ +#ifdef FLECS_PARSER + ecs_vector_t *result = NULL; + const char *ptr = expr; + ecs_term_t term = {0}; + + if (!ptr) { + return NULL; + } + + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (term.name) { + ecs_parser_error(name, expr, (ptr - expr), + "column names not supported in type expression"); + goto error; + } + + if (term.oper != EcsAnd && term.oper != EcsAndFrom) { + ecs_parser_error(name, expr, (ptr - expr), + "operator other than AND not supported in type expression"); + goto error; + } + + if (ecs_term_finalize(world, name, expr, &term)) { + goto error; + } + + if (term.args[0].entity == 0) { + /* Empty term */ + goto done; + } + + if (term.args[0].set.mask != EcsDefaultSet) { + ecs_parser_error(name, expr, (ptr - expr), + "source modifiers not supported for type expressions"); + goto error; + } + + if (term.args[0].entity != EcsThis) { + ecs_parser_error(name, expr, (ptr - expr), + "subject other than this not supported in type expression"); + goto error; + } + + if (term.oper == EcsAndFrom) { + term.role = ECS_AND; + } + + ecs_id_t* elem = ecs_vector_add(&result, ecs_id_t); + *elem = term.id | term.role; + + ecs_term_fini(&term); + } + + result = sort_and_dedup(result); + +done: + return result; +error: + ecs_term_fini(&term); + ecs_vector_free(result); + return NULL; +#else + (void)world; + (void)name; + (void)expr; + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + return NULL; +#endif +} + +/* Create normalized type. A normalized type resolves all elements with an + * AND flag and appends them to the resulting type, where the default type + * maintains the original type hierarchy. */ +static +ecs_vector_t* ids_to_normalized_ids( + ecs_world_t *world, + ecs_vector_t *ids) +{ + ecs_vector_t *result = NULL; + + ecs_entity_t *array = ecs_vector_first(ids, ecs_id_t); + int32_t i, count = ecs_vector_count(ids); + + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, AND)) { + ecs_entity_t entity = ECS_PAIR_OBJECT(e); + + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, ECS_INVALID_PARAMETER, + "flag must be applied to type"); + + ecs_vector_each(type_ptr->normalized, ecs_id_t, c_ptr, { + ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); + *el = *c_ptr; + }) + } else { + ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); + *el = e; + } + } + + return sort_and_dedup(result); +} + +static +ecs_table_t* table_from_ids( + ecs_world_t *world, + ecs_vector_t *ids) +{ + ecs_ids_t ids_array = flecs_type_to_ids(ids); + ecs_table_t *result = flecs_table_find_or_create(world, &ids_array); + return result; +} + +/* If a name prefix is set with ecs_set_name_prefix, check if the entity name + * has the prefix, and if so remove it. This enables using prefixed names in C + * for components / systems while storing a canonical / language independent + * identifier. */ +const char* flecs_name_from_symbol( + ecs_world_t *world, + const char *type_name) +{ + const char *prefix = world->name_prefix; + if (type_name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(type_name, prefix, len) && + (isupper(type_name[len]) || type_name[len] == '_')) + { + if (type_name[len] == '_') { + return type_name + len + 1; + } else { + return type_name + len; + } + } + } + + return type_name; +} + +/* -- Public functions -- */ + +ecs_type_t ecs_type_from_str( + ecs_world_t *world, + const char *expr) +{ + ecs_vector_t *ids = expr_to_ids(world, NULL, expr); + if (!ids) { + return NULL; + } + + ecs_vector_t *normalized_ids = ids_to_normalized_ids(world, ids); + ecs_vector_free(ids); + + ecs_table_t *table = table_from_ids(world, normalized_ids); + ecs_vector_free(normalized_ids); + + return table->type; +} + +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *expr) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + ecs_vector_t *ids = expr_to_ids(world, NULL, expr); + if (!ids) { + return NULL; + } + + ecs_table_t *result = table_from_ids(world, ids); + ecs_vector_free(ids); + + return result; +} + +typedef struct ecs_hm_bucket_t { + ecs_vector_t *keys; + ecs_vector_t *values; +} ecs_hm_bucket_t; + +static +int32_t find_key( + ecs_hashmap_t map, + ecs_vector_t *keys, + ecs_size_t key_size, + const void *key) +{ + int32_t i, count = ecs_vector_count(keys); + void *key_array = ecs_vector_first_t(keys, key_size, 8); + for (i = 0; i < count; i ++) { + void *key_ptr = ECS_OFFSET(key_array, key_size * i); + if (map.compare(key_ptr, key) == 0) { + return i; + } + } + return -1; +} + +ecs_hashmap_t _flecs_hashmap_new( + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare) +{ + return (ecs_hashmap_t){ + .key_size = key_size, + .value_size = value_size, + .compare = compare, + .hash = hash, + .impl = ecs_map_new(ecs_hm_bucket_t, 0) + }; +} + +void flecs_hashmap_free( + ecs_hashmap_t map) +{ + ecs_map_iter_t it = ecs_map_iter(map.impl); + ecs_hm_bucket_t *bucket; + while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { + ecs_vector_free(bucket->keys); + ecs_vector_free(bucket->values); + } + + ecs_map_free(map.impl); +} + +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(map.impl, ecs_hm_bucket_t, hash); + if (!bucket) { + return NULL; + } + + int32_t index = find_key(map, bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } + + return ecs_vector_get_t(bucket->values, value_size, 8, index); +} + +flecs_hashmap_result_t _flecs_hashmap_ensure( + const ecs_hashmap_t map, + ecs_size_t key_size, + 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_ensure(map.impl, ecs_hm_bucket_t, hash); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + + void *value_ptr, *key_ptr; + + ecs_vector_t *keys = bucket->keys; + if (!keys) { + bucket->keys = ecs_vector_new_t(key_size, 8, 1); + bucket->values = ecs_vector_new_t(value_size, 8, 1); + key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); + ecs_os_memcpy(key_ptr, key, key_size); + value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); + ecs_os_memset(value_ptr, 0, value_size); + } else { + int32_t index = find_key(map, keys, key_size, key); + if (index == -1) { + key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); + ecs_os_memcpy(key_ptr, key, key_size); + value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(value_ptr, 0, value_size); + } else { + key_ptr = ecs_vector_get_t(bucket->keys, key_size, 8, index); + value_ptr = ecs_vector_get_t(bucket->values, value_size, 8, index); + } + } + + return (flecs_hashmap_result_t){ + .key = key_ptr, + .value = value_ptr, + .hash = hash + }; +} + +void _flecs_hashmap_set( + const 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); +} + +void _flecs_hashmap_remove_w_hash( + const ecs_hashmap_t map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash) +{ + ecs_hm_bucket_t *bucket = ecs_map_get(map.impl, ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } + + int32_t index = find_key(map, bucket->keys, key_size, key); + if (index == -1) { + return; + } + + ecs_vector_remove_t(bucket->keys, key_size, 8, index); + ecs_vector_remove_t(bucket->values, value_size, 8, index); + + if (!ecs_vector_count(bucket->keys)) { + ecs_vector_free(bucket->keys); + ecs_vector_free(bucket->values); + ecs_map_remove(map.impl, hash); + } +} + +void _flecs_hashmap_remove( + 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); + _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_vector_count(bucket->keys)) { + bucket = it->bucket = ecs_map_next(&it->it, ecs_hm_bucket_t, NULL); + if (!bucket) { + return NULL; + } + index = it->index = 0; + } + + if (key_out) { + *(void**)key_out = ecs_vector_get_t(bucket->keys, key_size, 8, index); + } + + return ecs_vector_get_t(bucket->values, value_size, 8, index); +} + +/* Global type variables */ +ecs_type_t ecs_type(EcsComponent); +ecs_type_t ecs_type(EcsType); +ecs_type_t ecs_type(EcsIdentifier); +ecs_type_t ecs_type(EcsQuery); +ecs_type_t ecs_type(EcsTrigger); +ecs_type_t ecs_type(EcsObserver); +ecs_type_t ecs_type(EcsPrefab); + +/* Component lifecycle actions for EcsIdentifier */ +static ECS_CTOR(EcsIdentifier, ptr, { + ptr->value = NULL; + ptr->hash = 0; + ptr->length = 0; +}) + +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; +}) + +static ECS_MOVE(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, NULL); + dst->value = src->value; + dst->hash = src->hash; + dst->length = src->length; + + src->value = NULL; + src->hash = 0; + src->length = 0; + +}) + +static ECS_ON_SET(EcsIdentifier, ptr, { + if (ptr->value) { + ptr->length = ecs_os_strlen(ptr->value); + ptr->hash = flecs_hash(ptr->value, ptr->length); + } else { + ptr->length = 0; + ptr->hash = 0; + } +}) + +/* Component lifecycle actions for EcsTrigger */ +static ECS_CTOR(EcsTrigger, ptr, { + ptr->trigger = NULL; +}) + +static ECS_DTOR(EcsTrigger, ptr, { + flecs_trigger_fini(world, (ecs_trigger_t*)ptr->trigger); +}) + +static ECS_COPY(EcsTrigger, dst, src, { + ecs_abort(ECS_INVALID_OPERATION, "Trigger component cannot be copied"); +}) + +static ECS_MOVE(EcsTrigger, dst, src, { + if (dst->trigger) { + flecs_trigger_fini(world, (ecs_trigger_t*)dst->trigger); + } + dst->trigger = src->trigger; + src->trigger = NULL; +}) + +/* Component lifecycle actions for EcsObserver */ +static ECS_CTOR(EcsObserver, ptr, { + ptr->observer = NULL; +}) + +static ECS_DTOR(EcsObserver, ptr, { + flecs_observer_fini(world, (ecs_observer_t*)ptr->observer); +}) + +static ECS_COPY(EcsObserver, dst, src, { + ecs_abort(ECS_INVALID_OPERATION, "Observer component cannot be copied"); +}) + +static ECS_MOVE(EcsObserver, dst, src, { + if (dst->observer) { + flecs_observer_fini(world, (ecs_observer_t*)dst->observer); + } + dst->observer = src->observer; + src->observer = NULL; +}) + +static +void register_on_delete(ecs_iter_t *it) { + ecs_id_t id = ecs_term_id(it, 1); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_id_record_t *r = flecs_ensure_id_record(it->world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete = ECS_PAIR_OBJECT(id); + + r = flecs_ensure_id_record(it->world, ecs_pair(e, EcsWildcard)); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete = ECS_PAIR_OBJECT(id); + + flecs_set_watch(it->world, e); + } +} + +static +void register_on_delete_object(ecs_iter_t *it) { + ecs_id_t id = ecs_term_id(it, 1); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_id_record_t *r = flecs_ensure_id_record(it->world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete_object = ECS_PAIR_OBJECT(id); + + flecs_set_watch(it->world, e); + } +} + +static +void on_set_component_lifecycle( ecs_iter_t *it) { + EcsComponentLifecycle *cl = ecs_term(it, EcsComponentLifecycle, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_set_component_actions_w_id(world, e, &cl[i]); + } +} + +/* -- Bootstrapping -- */ + +#define bootstrap_component(world, table, name)\ + _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) + +static +void _bootstrap_component( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = flecs_table_get_or_create_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *columns = data->columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = ecs_eis_ensure(world, entity); + record->table = table; + + int32_t index = flecs_table_append(world, table, data, entity, record, false); + record->row = index + 1; + + EcsComponent *component = ecs_vector_first(columns[0].data, EcsComponent); + 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_vector_first(columns[1].data, EcsIdentifier); + name_col[index].value = ecs_os_strdup(name); + name_col[index].length = name_length; + name_col[index].hash = flecs_hash(name, name_length); + + EcsIdentifier *symbol_col = ecs_vector_first(columns[2].data, EcsIdentifier); + symbol_col[index].value = ecs_os_strdup(symbol); + symbol_col[index].length = symbol_length; + symbol_col[index].hash = flecs_hash(symbol, symbol_length); +} + +/** Create type for component */ +ecs_type_t flecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_table_t *table = flecs_table_find_or_create(world, &(ecs_ids_t){ + .array = (ecs_entity_t[]){entity}, + .count = 1 + }); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +/** Bootstrap types for builtin components and tags */ +static +void bootstrap_types( + ecs_world_t *world) +{ + ecs_type(EcsComponent) = flecs_bootstrap_type(world, ecs_id(EcsComponent)); + ecs_type(EcsType) = flecs_bootstrap_type(world, ecs_id(EcsType)); + ecs_type(EcsIdentifier) = flecs_bootstrap_type(world, ecs_id(EcsIdentifier)); +} + +/** Initialize component table. This table is manually constructed to bootstrap + * flecs. After this function has been called, the builtin components can be + * created. + * The reason this table is constructed manually is because it requires the size + * and alignment of the EcsComponent and EcsIdentifier components, which haven't + * been created yet */ +static +ecs_table_t* bootstrap_component_table( + ecs_world_t *world) +{ + ecs_entity_t entities[] = { + ecs_id(EcsComponent), + ecs_pair(ecs_id(EcsIdentifier), EcsName), + ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), + ecs_pair(EcsChildOf, EcsFlecsCore) + }; + + ecs_ids_t array = { + .array = entities, + .count = 4 + }; + + ecs_table_t *result = flecs_table_find_or_create(world, &array); + ecs_data_t *data = flecs_table_get_or_create_data(result); + + /* Preallocate enough memory for initial components */ + data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); + data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); + + data->columns = ecs_os_malloc_n(ecs_column_t, 3); + ecs_assert(data->columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); + data->columns[0].size = ECS_SIZEOF(EcsComponent); + data->columns[0].alignment = ECS_ALIGNOF(EcsComponent); + data->columns[1].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + data->columns[1].size = ECS_SIZEOF(EcsIdentifier); + data->columns[1].alignment = ECS_ALIGNOF(EcsIdentifier); + data->columns[2].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + data->columns[2].size = ECS_SIZEOF(EcsIdentifier); + data->columns[2].alignment = ECS_ALIGNOF(EcsIdentifier); + + result->column_count = 3; + + return result; +} + +static +void 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_set_name(world, id, name); + ecs_set_symbol(world, id, symbol); + + ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add_pair(world, id, EcsChildOf, parent); + + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup_fullpath(world, name) == id, + ECS_INTERNAL_ERROR, NULL); + } +} + +void flecs_bootstrap( + ecs_world_t *world) +{ + ecs_type(EcsComponent) = NULL; + + ecs_trace_1("bootstrap core components"); + ecs_log_push(); + + /* Create table for initial components */ + ecs_table_t *table = bootstrap_component_table(world); + assert(table != NULL); + + bootstrap_component(world, table, EcsIdentifier); + bootstrap_component(world, table, EcsComponent); + bootstrap_component(world, table, EcsComponentLifecycle); + bootstrap_component(world, table, EcsType); + bootstrap_component(world, table, EcsQuery); + bootstrap_component(world, table, EcsTrigger); + bootstrap_component(world, table, EcsObserver); + + ecs_set_component_actions(world, EcsIdentifier, { + .ctor = ecs_ctor(EcsIdentifier), + .dtor = ecs_dtor(EcsIdentifier), + .copy = ecs_copy(EcsIdentifier), + .move = ecs_move(EcsIdentifier), + .on_set = ecs_on_set(EcsIdentifier) + }); + + ecs_set_component_actions(world, EcsTrigger, { + .ctor = ecs_ctor(EcsTrigger), + .dtor = ecs_dtor(EcsTrigger), + .copy = ecs_copy(EcsTrigger), + .move = ecs_move(EcsTrigger) + }); + + ecs_set_component_actions(world, EcsObserver, { + .ctor = ecs_ctor(EcsObserver), + .dtor = ecs_dtor(EcsObserver), + .copy = ecs_copy(EcsObserver), + .move = ecs_move(EcsObserver) + }); + + world->stats.last_component_id = EcsFirstUserComponentId; + world->stats.last_id = EcsFirstUserEntityId; + world->stats.min_id = 0; + world->stats.max_id = 0; + + bootstrap_types(world); + + ecs_set_scope(world, EcsFlecsCore); + + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); + + flecs_bootstrap_tag(world, EcsModule); + flecs_bootstrap_tag(world, EcsPrefab); + flecs_bootstrap_tag(world, EcsHidden); + flecs_bootstrap_tag(world, EcsDisabled); + + /* Initialize scopes */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); + + /* Initialize builtin entities */ + bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); + bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); + bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); + bootstrap_entity(world, EcsTransitive, "Transitive", EcsFlecsCore); + bootstrap_entity(world, EcsFinal, "Final", EcsFlecsCore); + bootstrap_entity(world, EcsTag, "Tag", EcsFlecsCore); + + bootstrap_entity(world, EcsIsA, "IsA", EcsFlecsCore); + bootstrap_entity(world, EcsChildOf, "ChildOf", EcsFlecsCore); + + bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); + bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); + bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); + bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); + + bootstrap_entity(world, EcsOnDelete, "OnDelete", EcsFlecsCore); + + // bootstrap_entity(world, EcsOnCreateTable, "OnCreateTable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteTable, "OnDeleteTable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); + // bootstrap_entity(world, EcsOnTableNonEmpty, "OnTableNonEmpty", EcsFlecsCore); + // bootstrap_entity(world, EcsOnCreateTrigger, "OnCreateTrigger", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteTrigger, "OnDeleteTrigger", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteObservable, "OnDeleteObservable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnComponentLifecycle, "OnComponentLifecycle", EcsFlecsCore); + + bootstrap_entity(world, EcsOnDeleteObject, "OnDeleteObject", EcsFlecsCore); + + bootstrap_entity(world, EcsRemove, "Remove", EcsFlecsCore); + bootstrap_entity(world, EcsDelete, "Delete", EcsFlecsCore); + bootstrap_entity(world, EcsThrow, "Throw", EcsFlecsCore); + + + /* Transitive relations */ + ecs_add_id(world, EcsIsA, EcsTransitive); + + /* Tag relations (relations that cannot have data) */ + ecs_add_id(world, EcsIsA, EcsTag); + ecs_add_id(world, EcsChildOf, EcsTag); + + /* Final components/relations */ + ecs_add_id(world, ecs_id(EcsComponent), EcsFinal); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsFinal); + ecs_add_id(world, EcsTransitive, EcsFinal); + ecs_add_id(world, EcsFinal, EcsFinal); + ecs_add_id(world, EcsIsA, EcsFinal); + ecs_add_id(world, EcsOnDelete, EcsFinal); + ecs_add_id(world, EcsOnDeleteObject, EcsFinal); + + + /* Define triggers for when relationship cleanup rules are assigned */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsOnDelete, EcsWildcard)}, + .callback = register_on_delete, + .events = {EcsOnAdd} + }); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsOnDeleteObject, EcsWildcard)}, + .callback = register_on_delete_object, + .events = {EcsOnAdd} + }); + + /* Define trigger for when component lifecycle is set for component */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_id(EcsComponentLifecycle)}, + .callback = on_set_component_lifecycle, + .events = {EcsOnSet} + }); + + /* Removal of ChildOf objects (parents) deletes the subject (child) */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); + + /* Run bootstrap functions for other parts of the code */ + flecs_bootstrap_hierarchy(world); + + ecs_set_scope(world, 0); + + ecs_log_pop(); +} + + +#define ECS_NAME_BUFFER_LENGTH (64) + +static +bool path_append( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t cur = 0; + char buff[22]; + const char *name; + + if (ecs_is_valid(world, child)) { + cur = ecs_get_object(world, child, EcsChildOf, 0); + if (cur) { + if (cur != parent && cur != EcsFlecsCore) { + path_append(world, parent, cur, sep, prefix, buf); + ecs_strbuf_appendstr(buf, sep); + } + } else if (prefix) { + ecs_strbuf_appendstr(buf, prefix); + } + + name = ecs_get_name(world, child); + if (!name) { + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; + } + } else { + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; + } + + ecs_strbuf_appendstr(buf, name); + + return cur != 0; +} + +static +ecs_string_t get_string_key( + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_assert(!length || length == ecs_os_strlen(name), + ECS_INTERNAL_ERROR, NULL); + + if (!length) { + length = ecs_os_strlen(name); + } + + ecs_assert(!hash || hash == flecs_hash(name, length), + ECS_INTERNAL_ERROR, NULL); + + if (!hash) { + hash = flecs_hash(name, length); + } + + return (ecs_string_t) { + .value = (char*)name, + .length = length, + .hash = hash + }; +} + +static +ecs_entity_t find_by_name( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_string_t key = get_string_key(name, length, hash); + + ecs_entity_t *e = flecs_hashmap_get(*map, &key, ecs_entity_t); + + if (!e) { + return 0; + } + + return *e; +} + +static +void register_by_name( + ecs_hashmap_t *map, + ecs_entity_t entity, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_string_t key = get_string_key(name, length, hash); + + ecs_entity_t existing = find_by_name(map, name, key.length, key.hash); + if (existing) { + if (existing != entity) { + ecs_abort(ECS_ALREADY_DEFINED, + "conflicting entity registered with name '%s'", name); + } + } else { + key.value = ecs_os_strdup(key.value); + } + + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + *map, &key, ecs_entity_t); + + *((ecs_entity_t*)hmr.value) = entity; +} + +static +bool is_number( + const char *name) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!isdigit(name[0])) { + return false; + } + + ecs_size_t i, length = ecs_os_strlen(name); + for (i = 1; i < length; i ++) { + char ch = name[i]; + + if (!isdigit(ch)) { + break; + } + } + + return i >= length; +} + +static +ecs_entity_t name_to_id( + const char *name) +{ + long int result = atol(name); + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + return (ecs_entity_t)result; +} + +static +ecs_entity_t get_builtin( + const char *name) +{ + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; + } + + return 0; +} + +static +ecs_entity_t find_child_in_table( + const ecs_table_t *table, + const char *name) +{ + /* If table doesn't have names, then don't bother */ + int32_t name_index = ecs_type_index_of(table->type, 0, + ecs_pair(ecs_id(EcsIdentifier), EcsName)); + if (name_index == -1) { + return 0; + } + + ecs_data_t *data = flecs_table_get_data(table); + if (!data || !data->columns) { + return 0; + } + + int32_t i, count = ecs_vector_count(data->entities); + if (!count) { + return 0; + } + + ecs_column_t *column = &data->columns[name_index]; + EcsIdentifier *names = ecs_vector_first(column->data, EcsIdentifier); + + if (is_number(name)) { + return name_to_id(name); + } + + for (i = 0; i < count; i ++) { + const char *cur_name = names[i].value; + if (cur_name && !strcmp(cur_name, name)) { + return *ecs_vector_get(data->entities, ecs_entity_t, i); + } + } + + return 0; +} + +static +bool 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* path_elem( + const char *path, + const char *sep, + int32_t *len) +{ + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t count = 0; + + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + } + + ecs_assert(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + + if (!template_nesting && is_sep(&ptr, sep)) { + break; + } + + count ++; + } + + if (len) { + *len = count; + } + + if (count) { + return ptr; + } else { + return NULL; + } +} + +static +ecs_entity_t get_parent_from_path( + const ecs_world_t *world, + ecs_entity_t parent, + const char **path_ptr, + const char *prefix, + bool new_entity) +{ + bool start_from_root = false; + const char *path = *path_ptr; + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + if (prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(path, prefix, len)) { + path += len; + parent = 0; + start_from_root = true; + } + } + + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); + } + + *path_ptr = path; + + return parent; +} + +static +void on_set_symbol(ecs_iter_t *it) { + EcsIdentifier *n = ecs_term(it, EcsIdentifier, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + register_by_name( + &world->symbols, e, n[i].value, n[i].length, n[i].hash); + } +} + +static +uint64_t string_hash( + const void *ptr) +{ + const ecs_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INVALID_PARAMETER, NULL); + return str->hash; +} + +static +int string_compare( + const void *ptr1, + const void *ptr2) +{ + const ecs_string_t *str1 = ptr1; + const ecs_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); +} + +ecs_hashmap_t flecs_string_hashmap_new(void) { + return flecs_hashmap_new(ecs_string_t, ecs_entity_t, + string_hash, + string_compare); +} + +void flecs_bootstrap_hierarchy(ecs_world_t *world) { + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol)}, + .callback = on_set_symbol, + .events = {EcsOnSet} + }); +} + + +/* Public functions */ + +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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + if (!sep) { + sep = "."; + } + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (parent != child) { + path_append(world, parent, child, sep, prefix, &buf); + } else { + ecs_strbuf_appendstr(&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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + ecs_entity_t result = 0; + + ecs_id_record_t *r = flecs_get_id_record(world, ecs_pair(EcsChildOf, parent)); + if (r && r->table_index) { + ecs_map_iter_t it = ecs_map_iter(r->table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + result = find_child_in_table(tr->table, name); + if (result) { + return result; + } + } + } + + return result; +} + +ecs_entity_t ecs_lookup( + const ecs_world_t *world, + const char *name) +{ + if (!name) { + return 0; + } + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = get_builtin(name); + if (e) { + return e; + } + + if (is_number(name)) { + return name_to_id(name); + } + + e = find_by_name(&world->aliases, name, 0, 0); + if (e) { + return e; + } + + return ecs_lookup_child(world, 0, name); +} + +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *name, + bool lookup_as_path) +{ + if (!name) { + return 0; + } + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = find_by_name(&world->symbols, name, 0, 0); + if (e) { + return e; + } + + if (lookup_as_path) { + return ecs_lookup_fullpath(world, name); + } + + 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; + } + + if (!sep) { + sep = "."; + } + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = get_builtin(path); + if (e) { + return e; + } + + e = find_by_name(&world->aliases, path, 0, 0); + if (e) { + return e; + } + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr, *ptr_start; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + ecs_entity_t cur; + bool core_searched = false; + + if (!sep) { + sep = "."; + } + + parent = get_parent_from_path(world, parent, &path, prefix, true); + +retry: + cur = parent; + ptr_start = ptr = path; + + while ((ptr = path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; + } + } + +tail: + if (!cur && recursive) { + if (!core_searched) { + if (parent) { + parent = ecs_get_object(world, parent, EcsChildOf, 0); + } else { + parent = EcsFlecsCore; + core_searched = true; + } + goto retry; + } + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +} + +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_entity_t e = ecs_pair(EcsChildOf, scope); + ecs_ids_t to_add = { + .array = &e, + .count = 1 + }; + + ecs_entity_t cur = stage->scope; + stage->scope = scope; + + if (scope) { + stage->scope_table = flecs_table_traverse_add( + world, &world->store.root, &to_add, NULL); + } else { + stage->scope_table = &world->store.root; + } + + return cur; +} + +ecs_entity_t ecs_get_scope( + const ecs_world_t *world) +{ + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->scope; +} + +int32_t ecs_get_child_count( + const ecs_world_t *world, + ecs_entity_t parent) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + int32_t count = 0; + + ecs_id_record_t *r = flecs_get_id_record(world, ecs_pair(EcsChildOf, parent)); + if (r && r->table_index) { + ecs_map_iter_t it = ecs_map_iter(r->table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + count += ecs_table_count(tr->table); + } + } + + return count; +} + +ecs_iter_t ecs_scope_iter_w_filter( + ecs_world_t *iter_world, + ecs_entity_t parent, + ecs_filter_t *filter) +{ + ecs_assert(iter_world != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_world_t *world = (ecs_world_t*)ecs_get_world(iter_world); + ecs_iter_t it = { + .world = iter_world + }; + + ecs_id_record_t *r = flecs_get_id_record( + world, ecs_pair(EcsChildOf, parent)); + if (r && r->table_index) { + it.iter.parent.tables = ecs_map_iter(r->table_index); + it.table_count = ecs_map_count(r->table_index); + if (filter) { + it.iter.parent.filter = *filter; + } + } + + return it; +} + +ecs_iter_t ecs_scope_iter( + ecs_world_t *iter_world, + ecs_entity_t parent) +{ + return ecs_scope_iter_w_filter(iter_world, parent, NULL); +} + +bool ecs_scope_next( + ecs_iter_t *it) +{ + ecs_scope_iter_t *iter = &it->iter.parent; + ecs_map_iter_t *tables = &iter->tables; + ecs_filter_t filter = iter->filter; + ecs_table_record_t *tr; + + while ((tr = ecs_map_next(tables, ecs_table_record_t, NULL))) { + ecs_table_t *table = tr->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + iter->index ++; + + ecs_data_t *data = flecs_table_get_data(table); + if (!data) { + continue; + } + + it->count = ecs_table_count(table); + if (!it->count) { + continue; + } + + if (filter.include || filter.exclude) { + if (!flecs_table_match_filter(it->world, table, &filter)) { + continue; + } + } + + it->table = table; + it->table_columns = data->columns; + it->count = ecs_table_count(table); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->is_valid = true; + + goto yield; + } + + it->is_valid = false; + return false; + +yield: + it->is_valid = true; + return true; +} + +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + const char *old_prefix = world->name_prefix; + world->name_prefix = prefix; + return old_prefix; +} + +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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!sep) { + sep = "."; + } + + if (!path) { + if (!entity) { + entity = ecs_new_id(world); + } + + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); + } + + return entity; + } + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr = path; + const char *ptr_start = path; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + + parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); + + ecs_entity_t cur = parent; + + char *name = NULL; + + while ((ptr = path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + 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 */ + if (entity && !path_elem(ptr, sep, NULL)) { + e = entity; + } + + if (!e) { + e = ecs_new_id(world); + } + + ecs_set_name(world, e, name); + + if (cur) { + ecs_add_pair(world, e, EcsChildOf, cur); + } + } + + cur = e; + } + + if (entity && (cur != entity)) { + if (name) { + ecs_os_free(name); + } + + name = ecs_os_strdup(elem); + + ecs_set_name(world, entity, name); + } + + if (name) { + ecs_os_free(name); + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +} + +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) +{ + if (!sep) { + sep = "."; + } + + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); +} + +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + register_by_name(&world->aliases, entity, name, 0, 0); +} diff --git a/fggl/ecs2/flecs/flecs.h b/fggl/ecs2/flecs/flecs.h new file mode 100644 index 0000000000000000000000000000000000000000..75448ecc18b673c97422f292d76697567e28c431 --- /dev/null +++ b/fggl/ecs2/flecs/flecs.h @@ -0,0 +1,18331 @@ +// 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 + +/* FLECS_LEGACY should be defined when building for C89 */ +// #define FLECS_LEGACY + +/* FLECS_NO_DEPRECATED_WARNINGS disables deprecated warnings */ +#define FLECS_NO_DEPRECATED_WARNINGS + +/* FLECS_NO_CPP should be defined when building for C++ without the C++ API */ +// #define FLECS_NO_CPP + +/* FLECS_CUSTOM_BUILD should be defined when manually selecting features */ +// #define FLECS_CUSTOM_BUILD + +/* FLECS_SANITIZE enables expensive checks that can detect issues early */ +#ifndef NDEBUG +#define FLECS_SANITIZE +#endif + +/* If this is a regular, non-custom build, build all modules and addons. */ +#ifndef FLECS_CUSTOM_BUILD +/* Modules */ +#define FLECS_SYSTEM +#define FLECS_PIPELINE +#define FLECS_TIMER + +/* Addons */ +#define FLECS_BULK +#define FLECS_MODULE +#define FLECS_PARSER +#define FLECS_PLECS +#define FLECS_QUEUE +#define FLECS_SNAPSHOT +#define FLECS_DIRECT_ACCESS +#define FLECS_STATS +#endif // ifndef FLECS_CUSTOM_BUILD + +/* Unconditionally include deprecated definitions until the rest of the codebase + * has caught up */ +#define FLECS_DEPRECATED + +/* Set to double or int to increase accuracy of time keeping. Note that when + * using an integer type, an application has to provide the delta_time values + * to the progress() function, as the code that measures time requires a + * floating point type. */ +#ifndef FLECS_FLOAT +#define FLECS_FLOAT float +#endif // FLECS_FLOAT + +/** + * @file api_defines.h + * @brief Supporting defines for the public API. + * + * This file contains constants / macro's 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 + +/* Standard library dependencies */ +#include <time.h> +#include <stdlib.h> +#include <assert.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> + +/* Non-standard but required. If not provided by platform, add manually. */ +#include <stdint.h> + +/* Contains macro's 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 flecs_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_API __declspec(dllexport) +#elif 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 + * whitebox testing of internal datastructures */ +#ifndef NDEBUG +#define FLECS_DBG_API FLECS_API +#else +#define FLECS_DBG_API +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Language support defines +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY +#include <stdbool.h> +#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 + +typedef uint32_t ecs_flags32_t; +typedef uint64_t ecs_flags64_t; + +/* Keep unsigned integers out of the codebase as they do more harm than good */ +typedef int32_t ecs_size_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<int64_t>(alignof(T)) +#elif defined(_MSC_VER) +#define ECS_ALIGNOF(T) (int64_t)__alignof(T) +#elif defined(__GNUC__) +#define ECS_ALIGNOF(T) (int64_t)__alignof__(T) +#else +#define ECS_ALIGNOF(T) ((int64_t)&((struct { char c; T d; } *)0)->d) +#endif + +#if defined(__GNUC__) +#define ECS_UNUSED __attribute__((unused)) +#else +#define ECS_UNUSED +#endif + +#ifndef FLECS_NO_DEPRECATED_WARNINGS +#if defined(__GNUC__) +#define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +#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) + +/* 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<T>(V)) +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Reserved component ids +//////////////////////////////////////////////////////////////////////////////// + +/** Builtin component ids */ +#define FLECS__EEcsComponent (1) +#define FLECS__EEcsComponentLifecycle (2) +#define FLECS__EEcsType (3) +#define FLECS__EEcsIdentifier (4) +#define FLECS__EEcsTrigger (6) +#define FLECS__EEcsQuery (7) +#define FLECS__EEcsObserver (8) +// #define FLECS__EEcsIterable (9) + +/* System module component ids */ +#define FLECS__EEcsSystem (10) +#define FLECS__EEcsTickSource (11) + +/** Pipeline module component ids */ +#define FLECS__EEcsPipelineQuery (12) + +/** Timer module component ids */ +#define FLECS__EEcsTimer (13) +#define FLECS__EEcsRateFilter (14) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity id macro's +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_ROLE_MASK (0xFFull << 56) +#define ECS_ENTITY_MASK (0xFFFFFFFFull) +#define ECS_GENERATION_MASK (0xFFFFull << 32) +#define ECS_GENERATION(e) ((e & ECS_GENERATION_MASK) >> 32) +#define ECS_GENERATION_INC(e) ((e & ~ECS_GENERATION_MASK) | ((0xFFFF & (ECS_GENERATION(e) + 1)) << 32)) +#define ECS_COMPONENT_MASK (~ECS_ROLE_MASK) +#define ECS_HAS_ROLE(e, role) ((e & ECS_ROLE_MASK) == ECS_##role) +#define ECS_PAIR_RELATION(e) (ecs_entity_t_hi(e & ECS_COMPONENT_MASK)) +#define ECS_PAIR_OBJECT(e) (ecs_entity_t_lo(e)) +#define ECS_HAS_RELATION(e, rel) (ECS_HAS_ROLE(e, PAIR) && (ECS_PAIR_RELATION(e) == rel)) + +#define ECS_HAS_PAIR_OBJECT(e, rel, obj)\ + (ECS_HAS_RELATION(e, rel) && ECS_PAIR_OBJECT(e) == obj) + +#define ECS_HAS(id, has_id)(\ + (id == has_id) ||\ + (ECS_HAS_PAIR_OBJECT(id, ECS_PAIR_RELATION(has_id), ECS_PAIR_OBJECT(has_id)))) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convert between C typenames and variables +//////////////////////////////////////////////////////////////////////////////// + +/** Translate C type to ecs_type_t variable. */ +#define ecs_type(T) FLECS__T##T + +/** Translate C type to id. */ +#define ecs_id(T) FLECS__E##T + +/** Translate C type to module struct. */ +#define ecs_module(T) FLECS__M##T + +/** Translate C type to module struct. */ +#define ecs_module_ptr(T) FLECS__M##T##_ptr + +/** Translate C type to module struct. */ +#define ecs_iter_action(T) FLECS__F##T + + +//////////////////////////////////////////////////////////////////////////////// +//// 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)) + +/* Get object from pair with the correct (current) generation count */ +#define ecs_pair_relation(world, pair) ecs_get_alive(world, ECS_PAIR_RELATION(pair)) +#define ecs_pair_object(world, pair) ecs_get_alive(world, ECS_PAIR_OBJECT(pair)) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convenience macro's for ctor, dtor, move and copy +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY + +/* Constructor / destructor convenience macro */ +#define ECS_XTOR_IMPL(type, postfix, var, ...)\ + void type##_##postfix(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *entity_ptr,\ + void *_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)entity_ptr;\ + (void)_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t entity = entity_ptr[i];\ + type *var = &((type*)_ptr)[i];\ + (void)entity;\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +/* Copy convenience macro */ +#define ECS_COPY_IMPL(type, dst_var, src_var, ...)\ + void type##_##copy(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *dst_entities,\ + const ecs_entity_t *src_entities,\ + void *_dst_ptr,\ + const void *_src_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)dst_entities;\ + (void)src_entities;\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t dst_entity = dst_entities[i];\ + ecs_entity_t src_entity = src_entities[i];\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_entity;\ + (void)src_entity;\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +/* Move convenience macro */ +#define ECS_MOVE_IMPL(type, dst_var, src_var, ...)\ + void type##_##move(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *dst_entities,\ + const ecs_entity_t *src_entities,\ + void *_dst_ptr,\ + void *_src_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)dst_entities;\ + (void)src_entities;\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t dst_entity = dst_entities[i];\ + ecs_entity_t src_entity = src_entities[i];\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_entity;\ + (void)src_entity;\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +/* Constructor / destructor convenience macro */ +#define ECS_ON_SET_IMPL(type, var, ...)\ + void type##_##on_set(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *entity_ptr,\ + void *_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)entity_ptr;\ + (void)_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t entity = entity_ptr[i];\ + type *var = &((type*)_ptr)[i];\ + (void)entity;\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Error codes +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_INVALID_OPERATION (1) +#define ECS_INVALID_PARAMETER (2) +#define ECS_INVALID_DELETE (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_THREAD_ERROR (10) +#define ECS_CYCLE_DETECTED (11) + +#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_COLUMN_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_HAS_NO_DATA (44) +#define ECS_COLUMN_TYPE_MISMATCH (45) +#define ECS_NO_OUT_COLUMNS (46) + +#define ECS_TYPE_NOT_AN_ENTITY (60) +#define ECS_TYPE_CONSTRAINT_VIOLATION (61) +#define ECS_TYPE_INVALID_CASE (62) + +#define ECS_INVALID_WHILE_ITERATING (70) +#define ECS_LOCKED_STORAGE (71) +#define ECS_INVALID_FROM_WORKER (72) + +#define ECS_DESERIALIZE_FORMAT_ERROR (80) + + +//////////////////////////////////////////////////////////////////////////////// +//// Deprecated constants +//////////////////////////////////////////////////////////////////////////////// + +/* These constants should no longer be used, but are required by the core to + * guarantee backwards compatibility */ +#define ECS_AND (ECS_ROLE | (0x79ull << 56)) +#define ECS_OR (ECS_ROLE | (0x78ull << 56)) +#define ECS_XOR (ECS_ROLE | (0x77ull << 56)) +#define ECS_NOT (ECS_ROLE | (0x76ull << 56)) + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file log.h + * @brief Internal logging API. + * + * Internal utility functions for tracing, warnings and errors. + */ + +#ifndef FLECS_LOG_H +#define FLECS_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Color macro's +//////////////////////////////////////////////////////////////////////////////// + +#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" + + +//////////////////////////////////////////////////////////////////////////////// +//// Tracing +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void _ecs_trace( + int level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_warn( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_err( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_fatal( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_deprecated( + const char *file, + int32_t line, + const char *msg); + +FLECS_API +void ecs_log_push(void); + +FLECS_API +void ecs_log_pop(void); + +#ifndef FLECS_LEGACY + +#define ecs_trace(lvl, ...)\ + _ecs_trace(lvl, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_warn(...)\ + _ecs_warn(__FILE__, __LINE__, __VA_ARGS__) + +#define ecs_err(...)\ + _ecs_err(__FILE__, __LINE__, __VA_ARGS__) + +#define ecs_fatal(...)\ + _ecs_fatal(__FILE__, __LINE__, __VA_ARGS__) + +#ifndef FLECS_NO_DEPRECATED_WARNINGS +#define ecs_deprecated(...)\ + _ecs_deprecated(__FILE__, __LINE__, __VA_ARGS__) +#else +#define ecs_deprecated(...) +#endif + +/* If no tracing verbosity is defined, pick default based on build config */ +#if !(defined(ECS_TRACE_0) || defined(ECS_TRACE_1) || defined(ECS_TRACE_2) || defined(ECS_TRACE_3)) +#if !defined(NDEBUG) +#define ECS_TRACE_3 /* Enable all tracing in debug mode. May slow things down */ +#else +#define ECS_TRACE_1 /* Only enable infrequent tracing in release mode */ +#endif +#endif + +#if defined(ECS_TRACE_3) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) ecs_trace(2, __VA_ARGS__); +#define ecs_trace_3(...) ecs_trace(3, __VA_ARGS__); + +#elif defined(ECS_TRACE_2) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) ecs_trace(2, __VA_ARGS__); +#define ecs_trace_3(...) + +#elif defined(ECS_TRACE_1) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) +#define ecs_trace_3(...) +#endif +#else +#define ecs_trace_1(...) +#define ecs_trace_2(...) +#define ecs_trace_3(...) +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Exceptions +//////////////////////////////////////////////////////////////////////////////// + +/** Get description for error code */ +FLECS_API +const char* ecs_strerror( + int32_t error_code); + +/** Abort */ +FLECS_API +void _ecs_abort( + int32_t error_code, + const char *file, + int32_t line, + const char *fmt, + ...); + +#define ecs_abort(error_code, ...)\ + _ecs_abort(error_code, __FILE__, __LINE__, __VA_ARGS__); abort() + +/** Assert */ +FLECS_API +void _ecs_assert( + bool condition, + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...); + +#ifdef NDEBUG +#define ecs_assert(condition, error_code, ...) +#else +#define ecs_assert(condition, error_code, ...)\ + _ecs_assert(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ + assert(condition) +#endif + +FLECS_API +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +#ifndef FLECS_LEGACY + +#define ecs_parser_error(name, expr, column, ...)\ + _ecs_parser_error(name, expr, column, __VA_ARGS__);\ + abort() + +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file vector.h + * @brief Vector datastructure. + * + * This is an implementation of a simple vector type. The vector is allocated in + * a single block of memory, with the element count, and allocated number of + * elements encoded in the block. As this vector is used for user-types it has + * been designed to support alignments higher than 8 bytes. This makes the size + * of the vector header variable in size. To reduce the overhead associated with + * retrieving or computing this size, the functions are wrapped in macro calls + * that compute the header size at compile time. + * + * The API provides a number of _t macro's, which accept a size and alignment. + * These macro's are used when no compile-time type is available. + * + * The vector guarantees contiguous access to its elements. When an element is + * removed from the vector, the last element is copied to the removed element. + * + * The API requires passing in the type of the vector. This type is used to test + * whether the size of the provided type equals the size of the type with which + * the vector was created. In release mode this check is not performed. + * + * When elements are added to the vector, it will automatically resize to the + * next power of two. This can change the pointer of the vector, which is why + * operations that can increase the vector size, accept a double pointer to the + * vector. + */ + +#ifndef FLECS_VECTOR_H +#define FLECS_VECTOR_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public, so we can do compile-time header size calculation */ +struct ecs_vector_t { + int32_t count; + int32_t size; + +#ifndef NDEBUG + int64_t elem_size; +#endif +}; + +/* Compute the header size of the vector from size & alignment */ +#define ECS_VECTOR_U(size, alignment) size, ECS_CAST(int16_t, ECS_MAX(ECS_SIZEOF(ecs_vector_t), alignment)) + +/* Compute the header size of the vector from a provided compile-time type */ +#define ECS_VECTOR_T(T) ECS_VECTOR_U(ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +/* Utility macro's for creating vector on stack */ +#ifndef NDEBUG +#define ECS_VECTOR_VALUE(T, elem_count)\ +{\ + .elem_size = (int32_t)(ECS_SIZEOF(T)),\ + .count = elem_count,\ + .size = elem_count\ +} +#else +#define ECS_VECTOR_VALUE(T, elem_count)\ +{\ + .count = elem_count,\ + .size = elem_count\ +} +#endif + +#define ECS_VECTOR_DECL(name, T, elem_count)\ +struct {\ + union {\ + ecs_vector_t vector;\ + uint64_t align;\ + } header;\ + T array[elem_count];\ +} __##name##_value = {\ + .header.vector = ECS_VECTOR_VALUE(T, elem_count)\ +};\ +const ecs_vector_t *name = (ecs_vector_t*)&__##name##_value + +#define ECS_VECTOR_IMPL(name, T, elems, elem_count)\ +ecs_os_memcpy(__##name##_value.array, elems, sizeof(T) * elem_count) + +#define ECS_VECTOR_STACK(name, T, elems, elem_count)\ +ECS_VECTOR_DECL(name, T, elem_count);\ +ECS_VECTOR_IMPL(name, T, elems, elem_count) + +typedef struct ecs_vector_t ecs_vector_t; + +typedef int (*ecs_comparator_t)( + const void* p1, + const void *p2); + +/** Create new vector. */ +FLECS_API +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_new(T, elem_count) \ + _ecs_vector_new(ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_new_t(size, alignment, elem_count) \ + _ecs_vector_new(ECS_VECTOR_U(size, alignment), elem_count) + +/* Create new vector, initialize it with provided array */ +FLECS_API +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array); + +#define ecs_vector_from_array(T, elem_count, array)\ + _ecs_vector_from_array(ECS_VECTOR_T(T), elem_count, array) + +/* Initialize vector with zero's */ +FLECS_API +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_zero(vector, T) \ + _ecs_vector_zero(vector, ECS_VECTOR_T(T)) + +/** Free vector */ +FLECS_API +void ecs_vector_free( + ecs_vector_t *vector); + +/** Clear values in vector */ +FLECS_API +void ecs_vector_clear( + ecs_vector_t *vector); + +/** Assert when the provided size does not match the vector type. */ +FLECS_API +void ecs_vector_assert_size( + ecs_vector_t* vector_inout, + ecs_size_t elem_size); + +/** Assert when the provided alignment does not match the vector type. */ +FLECS_API +void ecs_vector_assert_alignment( + ecs_vector_t* vector, + ecs_size_t elem_alignment); + +/** Add element to vector. */ +FLECS_API +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_add(vector, T) \ + ((T*)_ecs_vector_add(vector, ECS_VECTOR_T(T))) + +#define ecs_vector_add_t(vector, size, alignment) \ + _ecs_vector_add(vector, ECS_VECTOR_U(size, alignment)) + +/** Add n elements to the vector. */ +FLECS_API +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_addn(vector, T, elem_count) \ + ((T*)_ecs_vector_addn(vector, ECS_VECTOR_T(T), elem_count)) + +#define ecs_vector_addn_t(vector, size, alignment, elem_count) \ + _ecs_vector_addn(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Get element from vector. */ +FLECS_API +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_get(vector, T, index) \ + ((T*)_ecs_vector_get(vector, ECS_VECTOR_T(T), index)) + +#define ecs_vector_get_t(vector, size, alignment, index) \ + _ecs_vector_get(vector, ECS_VECTOR_U(size, alignment), index) + +/** Get last element from vector. */ +FLECS_API +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_last(vector, T) \ + (T*)_ecs_vector_last(vector, ECS_VECTOR_T(T)) + +#define ecs_vector_last_t(vector, size, alignment) \ + _ecs_vector_last(vector, ECS_VECTOR_U(size, alignment)) + +/** Set minimum size for vector. If the current size of the vector is larger, + * the function will have no side effects. */ +FLECS_API +int32_t _ecs_vector_set_min_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_min_size(vector, T, size) \ + _ecs_vector_set_min_size(vector, ECS_VECTOR_T(T), size) + +/** Set minimum count for vector. If the current count of the vector is larger, + * the function will have no side effects. */ +FLECS_API +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_min_count(vector, T, size) \ + _ecs_vector_set_min_count(vector, ECS_VECTOR_T(T), size) + +/** Remove last element. This operation requires no swapping of values. */ +FLECS_API +void ecs_vector_remove_last( + ecs_vector_t *vector); + +/** Remove last value, store last element in provided value. */ +FLECS_API +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value); + +#define ecs_vector_pop(vector, T, value) \ + _ecs_vector_pop(vector, ECS_VECTOR_T(T), value) + +/** Append element at specified index to another vector. */ +FLECS_API +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_move_index(dst, src, T, index) \ + _ecs_vector_move_index(dst, src, ECS_VECTOR_T(T), index) + +/** Remove element at specified index. Moves the last value to the index. */ +FLECS_API +int32_t _ecs_vector_remove( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_remove(vector, T, index) \ + _ecs_vector_remove(vector, ECS_VECTOR_T(T), index) + +#define ecs_vector_remove_t(vector, size, alignment, index) \ + _ecs_vector_remove(vector, ECS_VECTOR_U(size, alignment), index) + +/** Shrink vector to make the size match the count. */ +FLECS_API +void _ecs_vector_reclaim( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_reclaim(vector, T)\ + _ecs_vector_reclaim(vector, ECS_VECTOR_T(T)) + +/** Grow size of vector with provided number of elements. */ +FLECS_API +int32_t _ecs_vector_grow( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_grow(vector, T, size) \ + _ecs_vector_grow(vector, ECS_VECTOR_T(T), size) + +/** Set allocation size of vector. */ +FLECS_API +int32_t _ecs_vector_set_size( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_size(vector, T, elem_count) \ + _ecs_vector_set_size(vector, ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_set_size_t(vector, size, alignment, elem_count) \ + _ecs_vector_set_size(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Set count of vector. If the size of the vector is smaller than the provided + * count, the vector is resized. */ +FLECS_API +int32_t _ecs_vector_set_count( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_count(vector, T, elem_count) \ + _ecs_vector_set_count(vector, ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_set_count_t(vector, size, alignment, elem_count) \ + _ecs_vector_set_count(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Return number of elements in vector. */ +FLECS_API +int32_t ecs_vector_count( + const ecs_vector_t *vector); + +/** Return size of vector. */ +FLECS_API +int32_t ecs_vector_size( + const ecs_vector_t *vector); + +/** Return first element of vector. */ +FLECS_API +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_first(vector, T) \ + ((T*)_ecs_vector_first(vector, ECS_VECTOR_T(T))) + +#define ecs_vector_first_t(vector, size, alignment) \ + _ecs_vector_first(vector, ECS_VECTOR_U(size, alignment)) + +/** Sort elements in vector. */ +FLECS_API +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action); + +#define ecs_vector_sort(vector, T, compare_action) \ + _ecs_vector_sort(vector, ECS_VECTOR_T(T), compare_action) + +/** Return memory occupied by vector. */ +FLECS_API +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used); + +#define ecs_vector_memory(vector, T, allocd, used) \ + _ecs_vector_memory(vector, ECS_VECTOR_T(T), allocd, used) + +#define ecs_vector_memory_t(vector, size, alignment, allocd, used) \ + _ecs_vector_memory(vector, ECS_VECTOR_U(size, alignment), allocd, used) + +/** Copy vectors */ +FLECS_API +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_copy(src, T) \ + _ecs_vector_copy(src, ECS_VECTOR_T(T)) + +#define ecs_vector_copy_t(src, size, alignment) \ + _ecs_vector_copy(src, ECS_VECTOR_U(size, alignment)) + +#ifndef FLECS_LEGACY +#define ecs_vector_each(vector, T, var, ...)\ + {\ + int var##_i, var##_count = ecs_vector_count(vector);\ + T* var##_array = ecs_vector_first(vector, T);\ + for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ + T* var = &var##_array[var##_i];\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + + +/** C++ wrapper for vector class. */ +#ifdef __cplusplus +#ifndef FLECS_NO_CPP + +#include <initializer_list> + +namespace flecs { + +template <typename T> +class vector_iterator +{ +public: + explicit vector_iterator(T* value, int index) { + m_value = value; + m_index = index; + } + + bool operator!=(vector_iterator const& other) const + { + return m_index != other.m_index; + } + + T const& operator*() const + { + return m_value[m_index]; + } + + vector_iterator& operator++() + { + ++m_index; + return *this; + } + +private: + T* m_value; + int m_index; +}; + +/* C++ class mainly used as wrapper around internal ecs_vector_t. Do not use + * this class as a replacement for STL datastructures! */ +template <typename T> +class vector { +public: + explicit vector(ecs_vector_t *v) : m_vector( v ) { } + + vector(size_t count = 0) : m_vector( nullptr ) { + if (count) { + init(count); + } + } + + vector(std::initializer_list<T> elems) : m_vector( nullptr) { + init(elems.size()); + *this = elems; + } + + void operator=(std::initializer_list<T> elems) { + for (auto elem : elems) { + this->add(elem); + } + } + + T& operator[](size_t index) { + return *static_cast<T*>(_ecs_vector_get(m_vector, ECS_VECTOR_T(T), index)); + } + + vector_iterator<T> begin() { + return vector_iterator<T>( + static_cast<T*>(_ecs_vector_first(m_vector, ECS_VECTOR_T(T))), 0); + } + + vector_iterator<T> end() { + return vector_iterator<T>( + static_cast<T*>(_ecs_vector_last(m_vector, ECS_VECTOR_T(T))), + ecs_vector_count(m_vector)); + } + + void clear() { + ecs_vector_clear(m_vector); + } + + void destruct() { + ecs_vector_free(m_vector); + } + + void add(T& value) { + T* elem = static_cast<T*>(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))); + *elem = value; + } + + void add(T&& value) { + T* elem = static_cast<T*>(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))) + *elem = value; + } + + T& get(int32_t index) { + ecs_assert(index < ecs_vector_count(m_vector), ECS_OUT_OF_RANGE, NULL); + return *static_cast<T*>(_ecs_vector_get(m_vector, ECS_VECTOR_T(T), index)); + } + + T& first() { + return *static_cast<T*>(_ecs_vector_first(m_vector, ECS_VECTOR_T(T))); + } + + T& last() { + return *static_cast<T*>(_ecs_vector_last(m_vector, ECS_VECTOR_T(T))); + } + + int32_t count() { + return ecs_vector_count(m_vector); + } + + int32_t size() { + return ecs_vector_size(m_vector); + } + + ecs_vector_t *ptr() { + return m_vector; + } + + void ptr(ecs_vector_t *ptr) { + m_vector = ptr; + } + +private: + void init(size_t count) { + m_vector = ecs_vector_new(T, static_cast<ecs_size_t>(count)); + } + + ecs_vector_t *m_vector; +}; + +} + +#endif +#endif + +#endif +/** + * @file map.h + * @brief Map datastructure. + * + * Key-value datastructure. The map allows for fast retrieval of a payload for + * a 64-bit key. While it is not as fast as the sparse set, it is better at + * handling randomly distributed values. + * + * Payload is stored in bucket arrays. A bucket is computed from an id by + * using the (bucket_count - 1) as an AND-mask. The number of buckets is always + * a power of 2. Multiple keys will be stored in the same bucket. As a result + * the worst case retrieval performance of the map is O(n), though this is rare. + * On average lookup performance should equal O(1). + * + * The datastructure will automatically grow the number of buckets when the + * ratio between elements and buckets exceeds a certain threshold (LOAD_FACTOR). + * + * Note that while the implementation is a hashmap, it can only compute hashes + * for the provided 64 bit keys. This means that the provided keys must always + * be unique. If the provided keys are hashes themselves, it is the + * responsibility of the user to ensure that collisions are handled. + * + * In debug mode the map verifies that the type provided to the map functions + * matches the one used at creation time. + */ + +#ifndef FLECS_MAP_H +#define FLECS_MAP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_map_t ecs_map_t; +typedef uint64_t ecs_map_key_t; + +typedef struct ecs_map_iter_t { + const ecs_map_t *map; + struct ecs_bucket_t *bucket; + int32_t bucket_index; + int32_t element_index; + void *payload; +} ecs_map_iter_t; + +/** Create new map. */ +FLECS_API +ecs_map_t * _ecs_map_new( + ecs_size_t elem_size, + ecs_size_t alignment, + int32_t elem_count); + +#define ecs_map_new(T, elem_count)\ + _ecs_map_new(sizeof(T), ECS_ALIGNOF(T), elem_count) + +/** Get element for key, returns NULL if they key doesn't exist. */ +FLECS_API +void * _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +#define ecs_map_get(map, T, key)\ + (T*)_ecs_map_get(map, sizeof(T), (ecs_map_key_t)key) + +/** Get pointer element. This dereferences the map element as a pointer. This + * operation returns NULL when either the element does not exist or whether the + * pointer is NULL, and should therefore only be used when the application knows + * for sure that a pointer should never be NULL. */ +FLECS_API +void * _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key); + +#define ecs_map_get_ptr(map, T, key)\ + (T)_ecs_map_get_ptr(map, key) + +/** Test if map has key */ +FLECS_API +bool ecs_map_has( + const ecs_map_t *map, + ecs_map_key_t key); + +/** Get or create element for key. */ +FLECS_API +void * _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +#define ecs_map_ensure(map, T, key)\ + (T*)_ecs_map_ensure(map, sizeof(T), (ecs_map_key_t)key) + +/** Set element. */ +FLECS_API +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload); + +#define ecs_map_set(map, key, payload)\ + _ecs_map_set(map, sizeof(*payload), (ecs_map_key_t)key, payload); + +/** Free map. */ +FLECS_API +void ecs_map_free( + ecs_map_t *map); + +/** Remove key from map. */ +FLECS_API +void ecs_map_remove( + 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. */ +FLECS_API +int32_t ecs_map_count( + const ecs_map_t *map); + +/** Return number of buckets in map. */ +FLECS_API +int32_t ecs_map_bucket_count( + const ecs_map_t *map); + +/** Return iterator to map contents. */ +FLECS_API +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map); + +/** Obtain next element in map from iterator. */ +FLECS_API +void* _ecs_map_next( + ecs_map_iter_t* iter, + ecs_size_t elem_size, + ecs_map_key_t *key); + +#define ecs_map_next(iter, T, key) \ + (T*)_ecs_map_next(iter, sizeof(T), key) + +/** Obtain next pointer element from iterator. See ecs_map_get_ptr. */ +FLECS_API +void* _ecs_map_next_ptr( + ecs_map_iter_t* iter, + ecs_map_key_t *key); + +#define ecs_map_next_ptr(iter, T, key) \ + (T)_ecs_map_next_ptr(iter, key) + +/** Grow number of buckets in the map for specified number of elements. */ +FLECS_API +void ecs_map_grow( + ecs_map_t *map, + int32_t elem_count); + +/** Set number of buckets in the map for specified number of elements. */ +FLECS_API +void ecs_map_set_size( + ecs_map_t *map, + int32_t elem_count); + +/** Return memory occupied by map. */ +FLECS_API +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used); + +#ifndef FLECS_LEGACY +#define ecs_map_each(map, T, key, var, ...)\ + {\ + ecs_map_iter_t it = ecs_map_iter(map);\ + ecs_map_key_t key;\ + T* var;\ + (void)key;\ + (void)var;\ + while ((var = ecs_map_next(&it, T, &key))) {\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + +/** C++ wrapper for map. */ +#ifdef __cplusplus +#ifndef FLECS_NO_CPP + +#include <initializer_list> +#include <utility> + +namespace flecs { + +/* C++ class mainly used as wrapper around internal ecs_map_t. Do not use + * this class as a replacement for STL datastructures! */ +template <typename K, typename T> +class map { +public: + map(size_t count = 0) { + init(count); + } + + map(std::initializer_list<std::pair<K, T>> elems) { + init(elems.size()); + *this = elems; + } + + void operator=(std::initializer_list<std::pair<K, T>> elems) { + for (auto elem : elems) { + this->set(elem.first, elem.second); + } + } + + void clear() { + ecs_map_clear(m_map); + } + + int32_t count() { + return ecs_map_count(m_map); + } + + void set(K& key, T& value) { + _ecs_map_set(m_map, sizeof(T), reinterpret_cast<ecs_map_key_t>(key), &value); + } + + T& get(K& key) { + static_cast<T*>(_ecs_map_get(m_map, sizeof(T), + reinterpret_cast<ecs_map_key_t>(key))); + } + + void destruct() { + ecs_map_free(m_map); + } + +private: + void init(size_t count) { + m_map = ecs_map_new(T, static_cast<ecs_size_t>(count)); + } + + ecs_map_t *m_map; +}; + +} + +#endif +#endif + +#endif +/** + * @file strbuf.h + * @brief Utility for constructing strings. + * + * A buffer builds up a list of elements which individually can be up to N bytes + * large. While appending, data is added to these elements. More elements are + * added on the fly when needed. When an application calls ecs_strbuf_get, all + * elements are combined in one string and the element administration is freed. + * + * This approach prevents reallocs of large blocks of memory, and therefore + * copying large blocks of memory when appending to a large buffer. A buffer + * preallocates some memory for the element overhead so that for small strings + * there is hardly any overhead, while for large strings the overhead is offset + * by the reduced time spent on copying memory. + */ + +#ifndef FLECS_STRBUF_H_ +#define FLECS_STRBUF_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_STRBUF_INIT (ecs_strbuf_t){0} +#define ECS_STRBUF_ELEMENT_SIZE (511) +#define ECS_STRBUF_MAX_LIST_DEPTH (32) + +typedef struct ecs_strbuf_element { + bool buffer_embedded; + int32_t pos; + char *buf; + struct ecs_strbuf_element *next; +} ecs_strbuf_element; + +typedef struct ecs_strbuf_element_embedded { + ecs_strbuf_element super; + char buf[ECS_STRBUF_ELEMENT_SIZE + 1]; +} ecs_strbuf_element_embedded; + +typedef struct ecs_strbuf_element_str { + ecs_strbuf_element super; + char *alloc_str; +} ecs_strbuf_element_str; + +typedef struct ecs_strbuf_list_elem { + int32_t count; + const char *separator; +} ecs_strbuf_list_elem; + +typedef struct ecs_strbuf_t { + /* When set by an application, append will write to this buffer */ + char *buf; + + /* The maximum number of characters that may be printed */ + int32_t max; + + /* Size of elements minus current element */ + int32_t size; + + /* The number of elements in use */ + int32_t elementCount; + + /* Always allocate at least one element */ + ecs_strbuf_element_embedded firstElement; + + /* The current element being appended to */ + ecs_strbuf_element *current; + + /* Stack that keeps track of number of list elements, used for conditionally + * inserting a separator */ + ecs_strbuf_list_elem list_stack[ECS_STRBUF_MAX_LIST_DEPTH]; + int32_t list_sp; +} ecs_strbuf_t; + +/* Append format string to a buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_append( + 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 +bool 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 +bool ecs_strbuf_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +/* Append source buffer to destination buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer); + +/* Append string to buffer, transfer ownership to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *buffer, + char *str); + +/* Append string to buffer, do not free/modify string. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *buffer, + const char *str); + +/* Append n characters to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + +/* Return result string (also resets buffer) */ +FLECS_API +char *ecs_strbuf_get( + 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 formatted string as a new element in list */ +FLECS_API +bool ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...); + +/* Append string as a new element in list */ +FLECS_API +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file os_api.h + * @brief Operationg system abstractions. + * + * 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 + +#include <stdarg.h> +#include <errno.h> + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include <malloc.h> +#elif defined(__FreeBSD__) +#include <stdlib.h> +#else +#include <alloca.h> +#endif + +#if defined(_WIN32) +#define ECS_OS_WINDOWS +#elif defined(__linux__) +#define ECS_OS_LINUX +#elif defined(__APPLE__) && defined(__MACH__) +#define ECS_OS_DARWIN +#else +/* Unknown OS */ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_time_t { + uint32_t sec; + uint32_t nanosec; +} ecs_time_t; + +/* Allocation counters (not thread safe) */ +extern int64_t ecs_os_api_malloc_count; +extern int64_t ecs_os_api_realloc_count; +extern int64_t ecs_os_api_calloc_count; +extern int64_t ecs_os_api_free_count; + +/* Use handle types that _at least_ can store pointers */ +typedef uintptr_t ecs_os_thread_t; +typedef uintptr_t ecs_os_cond_t; +typedef uintptr_t ecs_os_mutex_t; +typedef uintptr_t ecs_os_dl_t; + +/* 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); + +/* Memory management */ +typedef +void* (*ecs_os_api_malloc_t)( + ecs_size_t size); + +typedef +void (*ecs_os_api_free_t)( + void *ptr); + +typedef +void* (*ecs_os_api_realloc_t)( + void *ptr, + ecs_size_t size); + +typedef +void* (*ecs_os_api_calloc_t)( + ecs_size_t size); + +typedef +char* (*ecs_os_api_strdup_t)( + const char *str); + +/* Threads */ +typedef +void* (*ecs_os_thread_callback_t)( + void*); + +typedef +ecs_os_thread_t (*ecs_os_api_thread_new_t)( + ecs_os_thread_callback_t callback, + void *param); + +typedef +void* (*ecs_os_api_thread_join_t)( + ecs_os_thread_t thread); + + +/* Atomic increment / decrement */ +typedef +int (*ecs_os_api_ainc_t)( + int32_t *value); + + +/* Mutex */ +typedef +ecs_os_mutex_t (*ecs_os_api_mutex_new_t)( + void); + +typedef +void (*ecs_os_api_mutex_lock_t)( + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_mutex_unlock_t)( + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_mutex_free_t)( + ecs_os_mutex_t mutex); + +/* Condition variable */ +typedef +ecs_os_cond_t (*ecs_os_api_cond_new_t)( + void); + +typedef +void (*ecs_os_api_cond_free_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_signal_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_broadcast_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_wait_t)( + ecs_os_cond_t cond, + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_sleep_t)( + int32_t sec, + int32_t nanosec); + +typedef +void (*ecs_os_api_get_time_t)( + ecs_time_t *time_out); + +/* Logging */ +typedef +void (*ecs_os_api_log_t)( + const char *fmt, + va_list args); + +/* Application termination */ +typedef +void (*ecs_os_api_abort_t)( + void); + +/* Dynamic libraries */ +typedef +ecs_os_dl_t (*ecs_os_api_dlopen_t)( + const char *libname); + +typedef +ecs_os_proc_t (*ecs_os_api_dlproc_t)( + ecs_os_dl_t lib, + const char *procname); + +typedef +void (*ecs_os_api_dlclose_t)( + ecs_os_dl_t lib); + +typedef +char* (*ecs_os_api_module_to_path_t)( + const char *module_id); + +/* Prefix members of struct with 'ecs_' as some system headers may define + * macro's for functions like "strdup", "log" or "_free" */ + +typedef struct ecs_os_api_t { + /* API init / deinit */ + ecs_os_api_init_t init_; + ecs_os_api_fini_t fini_; + + /* Memory management */ + ecs_os_api_malloc_t malloc_; + ecs_os_api_realloc_t realloc_; + ecs_os_api_calloc_t calloc_; + ecs_os_api_free_t free_; + + /* Strings */ + ecs_os_api_strdup_t strdup_; + + /* Threads */ + ecs_os_api_thread_new_t thread_new_; + ecs_os_api_thread_join_t thread_join_; + + /* Atomic incremenet / decrement */ + ecs_os_api_ainc_t ainc_; + ecs_os_api_ainc_t adec_; + + /* Mutex */ + ecs_os_api_mutex_new_t mutex_new_; + ecs_os_api_mutex_free_t mutex_free_; + ecs_os_api_mutex_lock_t mutex_lock_; + ecs_os_api_mutex_lock_t mutex_unlock_; + + /* Condition variable */ + ecs_os_api_cond_new_t cond_new_; + ecs_os_api_cond_free_t cond_free_; + ecs_os_api_cond_signal_t cond_signal_; + ecs_os_api_cond_broadcast_t cond_broadcast_; + ecs_os_api_cond_wait_t cond_wait_; + + /* Time */ + ecs_os_api_sleep_t sleep_; + ecs_os_api_get_time_t get_time_; + + /* Logging */ + ecs_os_api_log_t log_; + ecs_os_api_log_t log_error_; + ecs_os_api_log_t log_debug_; + ecs_os_api_log_t log_warning_; + + /* Application termination */ + ecs_os_api_abort_t abort_; + + /* Dynamic library loading */ + ecs_os_api_dlopen_t dlopen_; + ecs_os_api_dlproc_t dlproc_; + ecs_os_api_dlclose_t dlclose_; + + /* Overridable function that translates from a logical module id to a + * shared library filename */ + ecs_os_api_module_to_path_t module_to_dl_; + + /* Overridable function that translates from a logical module id to a + * path that contains module-specif resources or assets */ + ecs_os_api_module_to_path_t module_to_etc_; +} ecs_os_api_t; + +FLECS_API +extern ecs_os_api_t ecs_os_api; + +FLECS_API +void ecs_os_init(void); + +FLECS_API +void ecs_os_fini(void); + +FLECS_API +void ecs_os_set_api( + ecs_os_api_t *os_api); + +FLECS_API +void ecs_os_set_api_defaults(void); + +/* 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(_MSC_VER) || defined(__MINGW32__) +#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 + +#define ecs_os_strset(dst, src) ecs_os_free(*dst); *dst = ecs_os_strdup(src) + +#ifdef __cplusplus +#define ecs_os_strlen(str) static_cast<ecs_size_t>(strlen(str)) +#define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, static_cast<size_t>(num)) +#define ecs_os_memcmp(ptr1, ptr2, num) memcmp(ptr1, ptr2, static_cast<size_t>(num)) +#define ecs_os_memcpy(ptr1, ptr2, num) memcpy(ptr1, ptr2, static_cast<size_t>(num)) +#define ecs_os_memset(ptr, value, num) memset(ptr, value, static_cast<size_t>(num)) +#define ecs_os_memmove(ptr, value, num) memmove(ptr, value, static_cast<size_t>(num)) +#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(ptr, value, num) memmove(ptr, value, (size_t)(num)) +#endif + +#define ecs_os_memcpy_t(ptr1, ptr2, T) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T)) +#define ecs_os_memcpy_n(ptr1, ptr2, T, count) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T) * count) + +#define ecs_os_strcmp(str1, str2) strcmp(str1, str2) +#define ecs_os_memset_t(ptr, value, T) ecs_os_memset(ptr, value, ECS_SIZEOF(T)) +#define ecs_os_memset_n(ptr, value, T, count) ecs_os_memset(ptr, value, ECS_SIZEOF(T) * count) + +#define ecs_os_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) + +#if defined(_MSC_VER) +#define ecs_os_strcat(str1, str2) strcat_s(str1, INT_MAX, str2) +#define ecs_os_sprintf(ptr, ...) sprintf_s(ptr, INT_MAX, __VA_ARGS__) +#define ecs_os_vsprintf(ptr, fmt, args) vsprintf_s(ptr, INT_MAX, fmt, args) +#define ecs_os_strcpy(str1, str2) strcpy_s(str1, INT_MAX, str2) +#ifdef __cplusplus +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, static_cast<size_t>(num)) +#else +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, (size_t)(num)) +#endif +#else +#define ecs_os_strcat(str1, str2) strcat(str1, str2) +#define ecs_os_sprintf(ptr, ...) sprintf(ptr, __VA_ARGS__) +#define ecs_os_vsprintf(ptr, fmt, args) vsprintf(ptr, fmt, args) +#define ecs_os_strcpy(str1, str2) strcpy(str1, str2) +#ifdef __cplusplus +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, static_cast<size_t>(num)) +#else +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, (size_t)(num)) +#endif +#endif + +/* Files */ +#if defined(_MSC_VER) +#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) + +/* Atomic increment / decrement */ +#define ecs_os_ainc(value) ecs_os_api.ainc_(value) +#define ecs_os_adec(value) ecs_os_api.adec_(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_get_time(time_out) ecs_os_api.get_time_(time_out) + +/* Logging (use functions to avoid using variadic macro arguments) */ +FLECS_API +void ecs_os_log(const char *fmt, ...); + +FLECS_API +void ecs_os_warn(const char *fmt, ...); + +FLECS_API +void ecs_os_err(const char *fmt, ...); + +FLECS_API +void ecs_os_dbg(const char *fmt, ...); + +FLECS_API +const char* ecs_os_strerror(int err); + +/* 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) + +/* Sleep with floating point time */ +FLECS_API +void ecs_sleepf( + double t); + +/* Measure time since provided timestamp */ +FLECS_API +double ecs_time_measure( + ecs_time_t *start); + +/* Calculate difference between two timestamps */ +FLECS_API +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2); + +/* Convert time value to a double */ +FLECS_API +double ecs_time_to_double( + ecs_time_t t); + +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 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 Basic API types + * @{ + */ + +/** Pointer object returned by API. */ +typedef void ecs_object_t; + +/** An id. Ids are the things that can be added to an entity. An id can be an + * entity or pair, and can have an optional role. */ +typedef uint64_t ecs_id_t; + +/** An entity identifier. */ +typedef ecs_id_t ecs_entity_t; + +/** A vector containing component identifiers used to describe a type. */ +typedef const ecs_vector_t* ecs_type_t; + +/** A world is the container for all ECS data and supporting features. */ +typedef struct ecs_world_t ecs_world_t; + +/** A query allows for cached iteration over ECS data */ +typedef struct ecs_query_t ecs_query_t; + +/** A filter allows for uncached, ad hoc iteration over ECS data */ +typedef struct ecs_filter_t ecs_filter_t; + +/** A trigger reacts to events matching a single filter term */ +typedef struct ecs_trigger_t ecs_trigger_t; + +/** An observer reacts to events matching multiple filter terms */ +typedef struct ecs_observer_t ecs_observer_t; + +/* An iterator lets an application iterate entities across tables. */ +typedef struct ecs_iter_t ecs_iter_t; + +/** Refs cache data that lets them access components faster than ecs_get. */ +typedef struct ecs_ref_t ecs_ref_t; + +/** @} */ + + + +/** + * @defgroup constants API constants + * @{ + */ + +/* Maximum number of components to add/remove in a single operation */ +#define ECS_MAX_ADD_REMOVE (32) + +/* Maximum number of terms cached in static arrays */ +#define ECS_TERM_CACHE_SIZE (8) + +/* Maximum number of terms in desc (larger, as these are temp objects) */ +#define ECS_TERM_DESC_CACHE_SIZE (16) + +/* Maximum number of events to set in static array of trigger descriptor */ +#define ECS_TRIGGER_DESC_EVENT_COUNT_MAX (8) + +/** @} */ + + +/** + * @defgroup function_types Function Types + * @{ + */ + +/** Action callback for systems and triggers */ +typedef void (*ecs_iter_action_t)( + ecs_iter_t *it); + +typedef bool (*ecs_iter_next_action_t)( + ecs_iter_t *it); + +/** Callback used for sorting components */ +typedef int (*ecs_order_by_action_t)( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +/** Callback used for ranking types */ +typedef int32_t (*ecs_group_by_action_t)( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + void *ctx); + +/** 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); + +/** @} */ + + +/** + * @defgroup filter_types Types used to describe filters, terms and triggers + * @{ + */ + +/** Set flags describe if & how a matched entity should be substituted */ +#define EcsDefaultSet (0) /* Default set, SuperSet|Self for This subject */ +#define EcsSelf (1) /* Select self (inclusive) */ +#define EcsSuperSet (2) /* Select superset until predicate match */ +#define EcsSubSet (4) /* Select subset until predicate match */ +#define EcsCascade (8) /* Use breadth-first ordering of relations */ +#define EcsAll (16) /* Walk full super/subset, regardless of match */ +#define EcsNothing (32) /* Select from nothing */ + +/** Specify read/write access for term */ +typedef enum ecs_inout_kind_t { + EcsInOutDefault, + EcsInOut, + EcsIn, + EcsOut +} ecs_inout_kind_t; + +/** Specifies whether term identifier is a variable or entity */ +typedef enum ecs_var_kind_t { + EcsVarDefault, /* Variable if name is all caps, otherwise an entity */ + EcsVarIsEntity, /* Term is an entity */ + EcsVarIsVariable /* Term is a variable */ +} ecs_var_kind_t; + +/** Type describing an operator used in an signature of a system signature */ +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; + +/** Substitution with set parameters. + * These parameters allow for substituting a term id with its super- or subsets + * for a specified relationship. This enables functionality such as selecting + * components from a base (IsA) or a parent (ChildOf) in a single term */ +typedef struct ecs_term_set_t { + ecs_entity_t relation; /* Relationship to substitute (default = IsA) */ + uint8_t mask; /* Substitute as self, subset, superset */ + int32_t min_depth; /* Min depth of subset/superset substitution */ + int32_t max_depth; /* Max depth of subset/superset substitution */ +} ecs_term_set_t; + +/** Type that describes a single identifier in a term */ +typedef struct ecs_term_id_t { + ecs_entity_t entity; /* Entity (default = This) */ + char *name; /* Name (default = ".") */ + ecs_var_kind_t var; /* Is id a variable (default yes if name is + * all caps & entity is 0) */ + ecs_term_set_t set; /* Set substitution parameters */ +} ecs_term_id_t; + +/** Type that describes a single column in the system signature */ +typedef struct ecs_term_t { + ecs_id_t id; /* Can be used instead of pred, args and role to + * set component/pair id. If not set, it will be + * computed from predicate, object. If set, the + * subject cannot be set, or be set to This. */ + + ecs_inout_kind_t inout; /* Access to contents matched with term */ + ecs_term_id_t pred; /* Predicate of term */ + ecs_term_id_t args[2]; /* Subject (0), object (1) of term */ + ecs_oper_kind_t oper; /* Operator of term */ + ecs_id_t role; /* Role of term */ + char *name; /* Name of term */ + + int32_t index; /* Computed term index in filter which takes + * into account folded OR terms */ + + bool move; /* When true, this signals to ecs_term_copy that + * the resources held by this term may be moved + * into the destination term. */ +} ecs_term_t; + +/* Deprecated -- do not use! */ +typedef enum ecs_match_kind_t { + EcsMatchDefault = 0, + EcsMatchAll, + EcsMatchAny, + EcsMatchExact +} ecs_match_kind_t; + +/** Filters alllow for ad-hoc quick filtering of entity tables. */ +struct ecs_filter_t { + ecs_term_t *terms; /* Array containing terms for filter */ + int32_t term_count; /* Number of elements in terms array */ + int32_t term_count_actual; /* Processed count, which folds OR terms */ + + ecs_term_t term_cache[ECS_TERM_CACHE_SIZE]; /* Cache for small filters */ + bool term_cache_used; + + bool match_this; /* Has terms that match EcsThis */ + bool match_only_this; /* Has only terms that match EcsThis */ + + char *name; /* Name of filter (optional) */ + char *expr; /* Expression of filter (if provided) */ + + /* Deprecated fields -- do not use! */ + ecs_type_t include; + ecs_type_t exclude; + ecs_match_kind_t include_kind; + ecs_match_kind_t exclude_kind; +}; + + +/** A trigger reacts to events matching a single term */ +struct ecs_trigger_t { + ecs_term_t term; /* Term describing the trigger condition id */ + + /* Trigger events */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + int32_t event_count; + + ecs_iter_action_t action; /* Callback */ + + void *ctx; /* Callback context */ + void *binding_ctx; /* Binding context (for language bindings) */ + + ecs_ctx_free_t ctx_free; /* Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + + ecs_entity_t entity; /* Trigger entity */ + ecs_entity_t self; /* Entity associated with observer */ + + uint64_t id; /* Internal id */ +}; + + +/* An observer reacts to events matching a filter */ +struct ecs_observer_t { + ecs_filter_t filter; + + /* Triggers created by observer (array size same as number of terms) */ + ecs_entity_t *triggers; + + /* Observer events */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + int32_t event_count; + + ecs_iter_action_t action; /* Callback */ + + void *ctx; /* Callback context */ + void *binding_ctx; /* Binding context (for language bindings) */ + + ecs_ctx_free_t ctx_free; /* Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + + ecs_entity_t entity; /* Observer entity */ + ecs_entity_t self; /* Entity associated with observer */ + + uint64_t id; /* Internal id */ +}; + +/** @} */ + + +/** + * @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 +//////////////////////////////////////////////////////////////////////////////// + +/** A stage enables modification while iterating and from multiple threads */ +typedef struct ecs_stage_t ecs_stage_t; + +/** A table is where entities and components are stored */ +typedef struct ecs_table_t ecs_table_t; + +/** A record stores data to map an entity id to a location in a table */ +typedef struct ecs_record_t ecs_record_t; + +/** Table column */ +typedef struct ecs_column_t ecs_column_t; + +/** Table data */ +typedef struct ecs_data_t ecs_data_t; + +/* Sparse set */ +typedef struct ecs_sparse_t ecs_sparse_t; + +/* Switch list */ +typedef struct ecs_switch_t ecs_switch_t; + +/* Internal structure to lookup tables for a (component) id */ +typedef struct ecs_id_record_t ecs_id_record_t; + +//////////////////////////////////////////////////////////////////////////////// +//// Non-opaque types +//////////////////////////////////////////////////////////////////////////////// + +struct ecs_record_t { + ecs_table_t *table; /* Identifies a type (and table) in world */ + int32_t row; /* Table row of the entity */ +}; + +/** Cached reference. */ +struct ecs_ref_t { + ecs_entity_t entity; /**< Entity of the reference */ + ecs_entity_t component; /**< Component of the reference */ + void *table; /**< Last known table */ + int32_t row; /**< Last known location in table */ + int32_t alloc_count; /**< Last known alloc count of table */ + ecs_record_t *record; /**< Pointer to record, if in main stage */ + const void *ptr; /**< Cached ptr */ +}; + +/** Array of entity ids that, other than a type, can live on the stack */ +typedef struct ecs_ids_t { + ecs_entity_t *array; /**< An array with entity ids */ + int32_t count; /**< The number of entities in the array */ +} ecs_ids_t; + +typedef struct ecs_page_cursor_t { + int32_t first; + int32_t count; +} ecs_page_cursor_t; + +typedef struct ecs_page_iter_t { + int32_t offset; + int32_t limit; + int32_t remaining; +} ecs_page_iter_t; + +/** Table specific data for iterators */ +typedef struct ecs_iter_table_t { + int32_t *columns; /**< Mapping from query terms to table columns */ + ecs_table_t *table; /**< The current table. */ + ecs_data_t *data; /**< Table component data */ + ecs_entity_t *components; /**< Components in current table */ + ecs_type_t *types; /**< Components in current table */ + ecs_ref_t *references; /**< References to entities (from query) */ +} ecs_iter_table_t; + +/** Scope-iterator specific data */ +typedef struct ecs_scope_iter_t { + ecs_filter_t filter; + ecs_map_iter_t tables; + int32_t index; +} ecs_scope_iter_t; + +/** Term-iterator specific data */ +typedef struct ecs_term_iter_t { + ecs_term_t *term; + ecs_id_record_t *self_index; + ecs_id_record_t *set_index; + + ecs_map_iter_t iter; + bool iter_set; + + /* Storage */ + ecs_id_t id; + int32_t column; + ecs_type_t type; + ecs_entity_t subject; + ecs_size_t size; + void *ptr; +} ecs_term_iter_t; + +typedef enum ecs_filter_iter_kind_t { + EcsFilterIterEvalIndex, + EcsFilterIterEvalNone +} ecs_filter_iter_kind_t; + +/** Filter-iterator specific data */ +typedef struct ecs_filter_iter_t { + ecs_filter_t filter; + ecs_filter_iter_kind_t kind; + + /* For EcsFilterIterEvalIndex */ + ecs_term_iter_t term_iter; + int32_t min_term_index; +} ecs_filter_iter_t; + +/** Query-iterator specific data */ +typedef struct ecs_query_iter_t { + ecs_page_iter_t page_iter; + int32_t index; + int32_t sparse_smallest; + int32_t sparse_first; + int32_t bitset_first; +} ecs_query_iter_t; + +/** Query-iterator specific data */ +typedef struct ecs_snapshot_iter_t { + ecs_filter_t filter; + ecs_vector_t *tables; /* ecs_table_leaf_t */ + int32_t index; +} ecs_snapshot_iter_t; + +/* Inline arrays for queries with small number of components */ +typedef struct ecs_iter_cache_t { + ecs_id_t ids[ECS_TERM_CACHE_SIZE]; + ecs_type_t types[ECS_TERM_CACHE_SIZE]; + int32_t columns[ECS_TERM_CACHE_SIZE]; + ecs_entity_t subjects[ECS_TERM_CACHE_SIZE]; + ecs_size_t sizes[ECS_TERM_CACHE_SIZE]; + void *ptrs[ECS_TERM_CACHE_SIZE]; + + bool ids_alloc; + bool types_alloc; + bool columns_alloc; + bool subjects_alloc; + bool sizes_alloc; + bool ptrs_alloc; +} ecs_iter_cache_t; + +/** The ecs_iter_t struct allows applications to iterate tables. + * Queries and filters, among others, allow an application to iterate entities + * that match a certain set of components. Because of how data is stored + * internally, entities with a given set of components may be stored in multiple + * consecutive arrays, stored across multiple tables. The ecs_iter_t type + * enables iteration across tables. */ +struct ecs_iter_t { + ecs_world_t *world; /**< The world */ + ecs_world_t *real_world; /**< Actual world. This differs from world when using threads. */ + ecs_entity_t system; /**< The current system (if applicable) */ + ecs_entity_t event; /**< The event (if applicable) */ + ecs_id_t event_id; /**< The (component) id for the event */ + ecs_entity_t self; /**< Self entity (if set) */ + + ecs_table_t *table; /**< Current table */ + ecs_data_t *data; + + ecs_id_t *ids; + ecs_type_t *types; + int32_t *columns; + ecs_entity_t *subjects; + ecs_size_t *sizes; + void **ptrs; + + ecs_ref_t *references; + + ecs_query_t *query; /**< Current query being evaluated */ + int32_t table_count; /**< Active table count for query */ + int32_t inactive_table_count; /**< Inactive table count for query */ + int32_t column_count; /**< Number of columns for system */ + int32_t term_index; /**< Index of term that triggered an event. + * This field will be set to the 'index' field + * of a trigger/observer term. */ + + void *table_columns; /**< Table component data */ + ecs_entity_t *entities; /**< Entity identifiers */ + + void *param; /**< Param passed to ecs_run */ + void *ctx; /**< System context */ + void *binding_ctx; /**< Binding context */ + FLECS_FLOAT delta_time; /**< Time elapsed since last frame */ + FLECS_FLOAT delta_system_time;/**< Time elapsed since last system invocation */ + FLECS_FLOAT world_time; /**< Time elapsed since start of simulation */ + + int32_t frame_offset; /**< Offset relative to frame */ + int32_t offset; /**< Offset relative to current table */ + int32_t count; /**< Number of entities to process by system */ + int32_t total_count; /**< Total number of entities in table */ + + bool is_valid; /**< Set to true after first next() */ + + ecs_ids_t *triggered_by; /**< Component(s) that triggered the system */ + ecs_entity_t interrupted_by; /**< When set, system execution is interrupted */ + + union { + ecs_scope_iter_t parent; + ecs_term_iter_t term; + ecs_filter_iter_t filter; + ecs_query_iter_t query; + ecs_snapshot_iter_t snapshot; + } iter; /**< Iterator specific data */ + + ecs_iter_cache_t cache; /**< Inline arrays to reduce allocations */ +}; + +typedef enum EcsMatchFailureReason { + EcsMatchOk, + EcsMatchNotASystem, + EcsMatchSystemIsATask, + EcsMatchEntityIsDisabled, + EcsMatchEntityIsPrefab, + EcsMatchFromSelf, + EcsMatchFromOwned, + EcsMatchFromShared, + EcsMatchFromContainer, + EcsMatchFromEntity, + EcsMatchOrFromSelf, + EcsMatchOrFromOwned, + EcsMatchOrFromShared, + EcsMatchOrFromContainer, + EcsMatchNotFromSelf, + EcsMatchNotFromOwned, + EcsMatchNotFromShared, + EcsMatchNotFromContainer, +} EcsMatchFailureReason; + +typedef struct ecs_match_failure_t { + EcsMatchFailureReason reason; + int32_t column; +} ecs_match_failure_t; + +//////////////////////////////////////////////////////////////////////////////// +//// Function types +//////////////////////////////////////////////////////////////////////////////// + +typedef struct EcsComponentLifecycle EcsComponentLifecycle; + +/** Constructor/destructor. Used for initializing / deinitializing components. */ +typedef void (*ecs_xtor_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx); + +/** Copy is invoked when a component is copied into another component. */ +typedef void (*ecs_copy_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Move is invoked when a component is moved to another component. */ +typedef void (*ecs_move_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Copy ctor */ +typedef void (*ecs_copy_ctor_t)( + ecs_world_t *world, + ecs_entity_t component, + const EcsComponentLifecycle *callbacks, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Move ctor */ +typedef void (*ecs_move_ctor_t)( + ecs_world_t *world, + ecs_entity_t component, + const EcsComponentLifecycle *callbacks, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Invoked when setting a component */ +typedef void (*ecs_on_set_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx); + +#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 reserves entity ids for components. Regular entity ids will start after + * this constant. This affects performance of table traversal, as edges with ids + * lower than this constant are looked up in an array, whereas constants higher + * than this id are looked up in a map. Increasing this value can improve + * performance at the cost of (significantly) higher memory usage. */ +#define ECS_HI_COMPONENT_ID (256) /* Maximum number of components */ + +/** The maximum number of nested function calls before the core will throw a + * cycle detected error */ +#define ECS_MAX_RECURSION (512) + +//////////////////////////////////////////////////////////////////////////////// +//// Global type handles +//////////////////////////////////////////////////////////////////////////////// + +/** Type handles to builtin components */ +FLECS_API +extern ecs_type_t + ecs_type(EcsComponent), + ecs_type(EcsComponentLifecycle), + ecs_type(EcsType), + ecs_type(EcsIdentifier); + +/** This allows passing 0 as type to functions that accept types */ +#define FLECS__TNULL 0 +#define FLECS__T0 0 +#define FLECS__E0 0 + +//////////////////////////////////////////////////////////////////////////////// +//// Functions used in declarative (macro) API +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +char* ecs_module_path_from_c( + const char *c_name); + +FLECS_API +bool ecs_component_has_actions( + const ecs_world_t *world, + ecs_entity_t component); + +FLECS_API +void ecs_add_module_tag( + ecs_world_t *world, + ecs_entity_t module); + +//////////////////////////////////////////////////////////////////////////////// +//// Signature API +//////////////////////////////////////////////////////////////////////////////// + +bool ecs_identifier_is_0( + const char *id); + +bool ecs_identifier_is_var( + const char *id); + +/** Calculate offset from address */ +#ifdef __cplusplus +#define ECS_OFFSET(o, offset) reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(o)) + (static_cast<uintptr_t>(offset))) +#else +#define ECS_OFFSET(o, offset) (void*)(((uintptr_t)(o)) + ((uintptr_t)(offset))) +#endif + +#ifdef __cplusplus +} +#endif + +#endif +/** + * @file type.h + * @brief Type API. + * + * This API contains utilities for working with types. Types are vectors of + * component ids, and are used most prominently in the API to construct filters. + */ + +#ifndef FLECS_TYPE_H +#define FLECS_TYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API +ecs_type_t ecs_type_from_id( + ecs_world_t *world, + ecs_entity_t entity); + +FLECS_API +ecs_entity_t ecs_type_to_id( + const ecs_world_t *world, + ecs_type_t type); + +FLECS_API +char* ecs_type_str( + const ecs_world_t *world, + ecs_type_t type); + +FLECS_API +ecs_type_t ecs_type_from_str( + ecs_world_t *world, + const char *expr); + +FLECS_API +ecs_type_t ecs_type_merge( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t type_add, + ecs_type_t type_remove); + +FLECS_API +ecs_type_t ecs_type_add( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id); + +FLECS_API +ecs_type_t ecs_type_remove( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id); + +FLECS_API +int32_t ecs_type_index_of( + ecs_type_t type, + int32_t offset, + ecs_id_t id); + +FLECS_API +bool ecs_type_has_id( + const ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + bool owned); + +FLECS_API +int32_t ecs_type_match( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + ecs_entity_t *out); + +FLECS_API +bool ecs_type_has_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has); + +FLECS_API +bool ecs_type_owns_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has, + bool owned); + +FLECS_API +ecs_entity_t ecs_type_get_entity_for_xor( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t xor_tag); + +#ifdef __cplusplus +} +#endif + +#endif + + +/** + * @defgroup desc_types Types used for creating API constructs + * @{ + */ + +/** Used with ecs_entity_init */ +typedef struct ecs_entity_desc_t { + ecs_entity_t entity; /* Optional existing entity handle. */ + + const char *name; /* Name of the entity. If no entity is provided, an + * entity with this name will be looked up first. When + * an entity is provided, the name will be verified + * with the existing entity. */ + + const char *sep; /* Optional custom separator for hierarchical names */ + 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. */ + + /* Array of ids to add to the new or existing entity. */ + ecs_id_t add[ECS_MAX_ADD_REMOVE]; + + /* Array of ids to remove from the existing entity. */ + ecs_id_t remove[ECS_MAX_ADD_REMOVE]; + + /* String expression with components to add */ + const char *add_expr; + + /* String expression with components to remove */ + const char *remove_expr; +} ecs_entity_desc_t; + + +/** Used with ecs_component_init. */ +typedef struct ecs_component_desc_t { + ecs_entity_desc_t entity; /* Parameters for component entity */ + size_t size; /* Component size */ + size_t alignment; /* Component alignment */ +} ecs_component_desc_t; + + +/** Used with ecs_type_init. */ +typedef struct ecs_type_desc_t { + ecs_entity_desc_t entity; /* Parameters for type entity */ + ecs_id_t ids[ECS_MAX_ADD_REMOVE]; /* Ids to include in type */ + const char *ids_expr; /* Id expression to include in type */ +} ecs_type_desc_t; + + +/** Used with ecs_filter_init. */ +typedef struct ecs_filter_desc_t { + /* Terms of the filter. If a filter has more terms than + * ECS_TERM_CACHE_SIZE use terms_buffer */ + ecs_term_t terms[ECS_TERM_DESC_CACHE_SIZE]; + + /* For filters with lots of terms an outside array can be provided. */ + ecs_term_t *terms_buffer; + int32_t terms_buffer_count; + + /* Substitute IsA relationships by default. If true, any term with 'set' + * assigned to DefaultSet will be modified to Self|SuperSet(IsA). */ + bool substitute_default; + + /* Filter expression. Should not be set at the same time as terms array */ + const char *expr; + + /* Optional name of filter, used for debugging. If a filter is created for + * a system, the provided name should match the system name. */ + const char *name; +} ecs_filter_desc_t; + + +/** Used with ecs_query_init. */ +typedef struct ecs_query_desc_t { + /* Filter for the query */ + ecs_filter_desc_t filter; + + /* Component to be used by order_by */ + ecs_entity_t order_by_component; + + /* Callback used for ordering query results. If order_by_id is 0, the + * pointer provided to the callback will be NULL. If the callback is not + * set, results will not be ordered. */ + ecs_order_by_action_t order_by; + + /* Id to be used by group_by. This id is passed to the group_by function and + * can be used identify the part of an entity type that should be used for + * grouping. */ + ecs_id_t group_by_id; + + /* 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; + + /* Context to pass to group_by */ + void *group_by_ctx; + + /* Function to free group_by_ctx */ + ecs_ctx_free_t group_by_ctx_free; + + /* If set, the query will be created as a subquery. A subquery matches at + * most a subset of its parent query. Subqueries do not directly receive + * (table) notifications from the world. Instead parent queries forward + * results to subqueries. This can improve matching performance, as fewer + * queries need to be matched with new tables. + * Subqueries can be nested. */ + ecs_query_t *parent; + + /* INTERNAL PROPERTY - system to be associated with query. Do not set, as + * this will change in future versions. */ + ecs_entity_t system; +} ecs_query_desc_t; + + +/** Used with ecs_trigger_init. */ +typedef struct ecs_trigger_desc_t { + /* Entity to associate with trigger */ + ecs_entity_desc_t entity; + + /* Term specifying the id to subscribe for */ + ecs_term_t term; + + /* Filter expression. May only contain a single term. If this field is set, + * the term field is ignored. */ + const char *expr; + + /* Events to trigger on (OnAdd, OnRemove, OnSet, UnSet) */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + + /* Callback to invoke on an event */ + ecs_iter_action_t callback; + + /* Associate with entity */ + ecs_entity_t self; + + /* 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; +} ecs_trigger_desc_t; + + +/** Used with ecs_observer_init. */ +typedef struct ecs_observer_desc_t { + /* Entity to associate with observer */ + ecs_entity_desc_t entity; + + /* Filter for observer */ + ecs_filter_desc_t filter; + + /* Events to observe (OnAdd, OnRemove, OnSet, UnSet) */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + + /* Callback to invoke on an event */ + ecs_iter_action_t callback; + + /* Associate with entity */ + ecs_entity_t self; + + /* 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; +} ecs_observer_desc_t; + +/** @} */ + + +/** + * @defgroup builtin_components Builtin components + * @{ + */ + +/** A (string) identifier. */ +typedef struct EcsIdentifier { + char *value; + ecs_size_t length; + uint64_t hash; +} EcsIdentifier; + +/** Component information. */ +typedef struct EcsComponent { + ecs_size_t size; /* Component size */ + ecs_size_t alignment; /* Component alignment */ +} EcsComponent; + +/** Component that stores an ecs_type_t. + * This component allows for the creation of entities that represent a type, and + * therefore the creation of named types. This component is typically + * instantiated by ECS_TYPE. */ +typedef struct EcsType { + ecs_type_t type; /* Preserved nested types */ + ecs_type_t normalized; /* Union of type and nested AND types */ +} EcsType; + +/** Component that contains lifecycle callbacks for a component. */ +struct EcsComponentLifecycle { + ecs_xtor_t ctor; /* ctor */ + ecs_xtor_t dtor; /* dtor */ + ecs_copy_t copy; /* copy assignment */ + ecs_move_t move; /* move assignment */ + + void *ctx; /* User defined context */ + + /* Ctor + copy */ + ecs_copy_ctor_t copy_ctor; + + /* Ctor + move */ + ecs_move_ctor_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_ctor_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_ctor_t move_dtor; + + /* 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_on_set_t on_set; +}; + +/** Component that stores reference to trigger */ +typedef struct EcsTrigger { + const ecs_trigger_t *trigger; +} EcsTrigger; + +/** Component that stores reference to observer */ +typedef struct EcsObserver { + const ecs_observer_t *observer; +} EcsObserver; + +/** Component for storing a query */ +typedef struct EcsQuery { + ecs_query_t *query; +} EcsQuery; + +/** @} */ + + +/** + * @defgroup misc_types Miscalleneous types + * @{ + */ + +/** Type that contains information about the world. */ +typedef struct ecs_world_info_t { + ecs_entity_t last_component_id; /* Last issued component entity id */ + ecs_entity_t last_id; /* Last issued entity id */ + ecs_entity_t min_id; /* First allowed entity id */ + ecs_entity_t max_id; /* Last allowed entity id */ + + FLECS_FLOAT delta_time_raw; /* Raw delta time (no time scaling) */ + FLECS_FLOAT delta_time; /* Time passed to or computed by ecs_progress */ + FLECS_FLOAT time_scale; /* Time scale applied to delta_time */ + FLECS_FLOAT target_fps; /* Target fps */ + FLECS_FLOAT frame_time_total; /* Total time spent processing a frame */ + FLECS_FLOAT system_time_total; /* Total time spent in systems */ + FLECS_FLOAT merge_time_total; /* Total time spent in merges */ + FLECS_FLOAT world_time_total; /* Time elapsed in simulation */ + FLECS_FLOAT world_time_total_raw; /* Time elapsed in simulation (no scaling) */ + + int32_t frame_count_total; /* Total number of frames */ + int32_t merge_count_total; /* Total number of merges */ + int32_t pipeline_build_count_total; /* Total number of pipeline builds */ + int32_t systems_ran_frame; /* Total number of systems ran in last frame */ +} ecs_world_info_t; + +/** @} */ + +/* Only include deprecated definitions if deprecated addon is required */ +#ifdef FLECS_DEPRECATED +/** + * @file 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 + +#define ecs_typeid(T) FLECS__E##T + +#define ecs_entity(T) ecs_typeid(T) + +#define ecs_add_trait(world, entity, component, trait)\ + ecs_add_entity(world, entity, ecs_trait(component, trait)) + +#define ecs_remove_trait(world, entity, component, trait)\ + ecs_remove_entity(world, entity, ecs_trait(component, trait)) + +#define ecs_has_trait(world, entity, component, trait)\ + ecs_has_entity(world, entity, ecs_trait(component, trait)) + +#ifndef FLECS_LEGACY + +#define ecs_set_trait(world, entity, component, trait, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_trait(ecs_typeid(component), ecs_typeid(trait)), sizeof(trait), &(trait)__VA_ARGS__) + +#define ecs_set_trait_tag(world, entity, trait, component, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_trait(ecs_typeid(component), trait), sizeof(component), &(component)__VA_ARGS__) + +#endif + +#define ecs_get_trait(world, entity, component, trait)\ + ((trait*)ecs_get_id(world, entity, ecs_trait(ecs_typeid(component), ecs_typeid(trait)))) + +#define ecs_get_trait_tag(world, entity, trait, component)\ + ((component*)ecs_get_id(world, entity, ecs_trait(ecs_typeid(component), trait))) + +#define ECS_PREFAB(world, id, ...) \ + ecs_entity_t id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .add_expr = #__VA_ARGS__,\ + .add = {EcsPrefab}\ + });\ + (void)id + +#define ECS_ENTITY_EXTERN(id)\ + extern ecs_entity_t id + +#define ECS_ENTITY_DECLARE(id)\ + ecs_entity_t id + +#define ECS_ENTITY_DEFINE(world, id, ...)\ + id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .add_expr = #__VA_ARGS__\ + });\ + +#define ECS_ENTITY(world, id, ...)\ + ecs_entity_t id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .add_expr = #__VA_ARGS__\ + });\ + (void)id + +#define ECS_COMPONENT(world, id) \ + ecs_id_t ecs_id(id) = ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + (void)ecs_id(id);\ + (void)ecs_type(id) + +#define ECS_COMPONENT_EXTERN(id)\ + extern ecs_id_t ecs_id(id);\ + extern ecs_type_t ecs_type(id) + +#define ECS_COMPONENT_DECLARE(id)\ + ecs_id_t ecs_id(id);\ + ecs_type_t ecs_type(id) + +#define ECS_COMPONENT_DEFINE(world, id)\ + ecs_id(id) = ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + ecs_type(id) = ecs_type_from_entity(world, ecs_id(id)) + +#define ECS_TAG(world, id)\ + ecs_entity_t id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .symbol = #id\ + });\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &id, 1);\ + (void)ecs_type(id) + +#define ECS_TAG_EXTERN(id)\ + extern ecs_entity_t id;\ + extern ecs_type_t ecs_type(id) + +#define ECS_TAG_DECLARE(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +#define ECS_TAG_DEFINE(world, id)\ + id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .symbol = #id\ + });\ + ecs_type(id) = ecs_type_from_entity(world, id) + +#define ECS_TYPE(world, id, ...) \ + ecs_entity_t id = ecs_type_init(world, &(ecs_type_desc_t){\ + .entity.name = #id,\ + .ids_expr = #__VA_ARGS__\ + });\ + ecs_type_t ecs_type(id) = ecs_type_from_entity(world, id);\ + (void)id;\ + (void)ecs_type(id) + +#define ECS_TYPE_EXTERN(id)\ + extern ecs_entity_t id;\ + extern ecs_type_t ecs_type(id) + +#define ECS_TYPE_DECLARE(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +#define ECS_TYPE_DEFINE(world, id, ...)\ + id = ecs_type_init(world, &(ecs_type_desc_t){\ + .entity.name = #id,\ + .ids_expr = #__VA_ARGS__\ + });\ + ecs_type(id) = ecs_type_from_entity(world, id);\ + +#define ECS_COLUMN(it, type, id, column)\ + ecs_id_t ecs_id(type) = ecs_column_entity(it, column);\ + ecs_type_t ecs_type(type) = ecs_column_type(it, column);\ + type *id = ecs_column(it, type, column);\ + (void)ecs_id(type);\ + (void)ecs_type(type);\ + (void)id + +#define ECS_COLUMN_COMPONENT(it, id, column)\ + ecs_id_t ecs_id(id) = ecs_column_entity(it, column);\ + ecs_type_t ecs_type(id) = ecs_column_type(it, column);\ + (void)ecs_id(id);\ + (void)ecs_type(id) + +#define ECS_COLUMN_ENTITY(it, id, column)\ + ecs_entity_t id = ecs_column_entity(it, column);\ + ecs_type_t ecs_type(id) = ecs_column_type(it, column);\ + (void)id;\ + (void)ecs_type(id) + +#define ECS_IMPORT_COLUMN(it, module, column) \ + module *ecs_module_ptr(module) = ecs_column(it, module, column);\ + ecs_assert(ecs_module_ptr(module) != NULL, ECS_MODULE_UNDEFINED, #module);\ + ecs_assert(!ecs_is_owned(it, column), ECS_COLUMN_IS_NOT_SHARED, NULL);\ + module ecs_module(module) = *ecs_module_ptr(module);\ + module##ImportHandles(ecs_module(module)) + +#define ecs_new(world, type) ecs_new_w_type(world, ecs_type(type)) + +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_type(world, ecs_type(component), count) + +#define ecs_add(world, entity, component)\ + ecs_add_type(world, entity, ecs_type(component)) + +#define ecs_remove(world, entity, type)\ + ecs_remove_type(world, entity, ecs_type(type)) + +#define ecs_add_remove(world, entity, to_add, to_remove)\ + ecs_add_remove_type(world, entity, ecs_type(to_add), ecs_type(to_remove)) + +#define ecs_has(world, entity, type)\ + ecs_has_type(world, entity, ecs_type(type)) + +#define ecs_owns(world, entity, type, owned)\ + ecs_type_owns_type(world, ecs_get_type(world, entity), ecs_type(type), owned) + +#define ecs_set_ptr_w_id(world, entity, size, ptr)\ + ecs_set_id(world, entity, size, ptr) + +#define ecs_owns_entity(world, entity, id, owned)\ + ecs_type_has_id(world, ecs_get_type(world, entity), id, owned) + +typedef ecs_ids_t ecs_entities_t; + +ECS_DEPRECATED("deprecated functionality") +FLECS_API +void ecs_dim_type( + ecs_world_t *world, + ecs_type_t type, + int32_t entity_count); + +ECS_DEPRECATED("use ecs_new_w_id") +FLECS_API +ecs_entity_t ecs_new_w_type( + ecs_world_t *world, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_bulk_new_w_id") +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_type( + ecs_world_t *world, + ecs_type_t type, + int32_t count); + +ECS_DEPRECATED("use ecs_add_id") +FLECS_API +void ecs_add_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_remove_id") +FLECS_API +void ecs_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_add_remove_id") +FLECS_API +void ecs_add_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t to_add, + ecs_type_t to_remove); + +ECS_DEPRECATED("use ecs_has_id") +FLECS_API +bool ecs_has_type( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_count_filter") +FLECS_API +int32_t ecs_count_type( + const ecs_world_t *world, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_count_id") +FLECS_API +int32_t ecs_count_entity( + const ecs_world_t *world, + ecs_id_t entity); + +ECS_DEPRECATED("use ecs_count_filter") +FLECS_API +int32_t ecs_count_w_filter( + const ecs_world_t *world, + const ecs_filter_t *filter); + +ECS_DEPRECATED("use ecs_set_component_actions_w_entity") +FLECS_API +void ecs_set_component_actions_w_entity( + ecs_world_t *world, + ecs_id_t id, + EcsComponentLifecycle *actions); + +ECS_DEPRECATED("use ecs_new_w_id") +FLECS_API +ecs_entity_t ecs_new_w_entity( + ecs_world_t *world, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_bulk_new_w_id") +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_entity( + ecs_world_t *world, + ecs_id_t id, + int32_t count); + +ECS_DEPRECATED("use ecs_enable_component_w_id") +FLECS_API +void ecs_enable_component_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable); + +ECS_DEPRECATED("use ecs_is_component_enabled_w_id") +FLECS_API +bool ecs_is_component_enabled_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_id") +FLECS_API +const void* ecs_get_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_id") +FLECS_API +const void* ecs_get_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_ref_w_id") +FLECS_API +const void* ecs_get_ref_w_entity( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_mut_id") +FLECS_API +void* ecs_get_mut_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added); + +ECS_DEPRECATED("use ecs_get_mut_id") +FLECS_API +void* ecs_get_mut_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added); + +ECS_DEPRECATED("use ecs_modified_id") +FLECS_API +void ecs_modified_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_modified_id") +void ecs_modified_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_set_id") +FLECS_API +ecs_entity_t ecs_set_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr); + +ECS_DEPRECATED("use ecs_has_id") +FLECS_API +bool ecs_has_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_id_str") +FLECS_API +size_t ecs_entity_str( + const ecs_world_t *world, + ecs_id_t entity, + char *buffer, + size_t buffer_len); + +ECS_DEPRECATED("use ecs_get_object_w_id(world, entity, EcsChildOf, id)") +FLECS_API +ecs_entity_t ecs_get_parent_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +#define ecs_get_parent(world, entity, component)\ + ecs_get_parent_w_entity(world, entity, ecs_typeid(component)) + +ECS_DEPRECATED("use ecs_get_stage_id") +FLECS_API +int32_t ecs_get_thread_index( + const ecs_world_t *world); + +ECS_DEPRECATED("use ecs_add_id") +FLECS_API +void ecs_add_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t entity_add); + +ECS_DEPRECATED("use ecs_remove_id") +FLECS_API +void ecs_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_add_id / ecs_remove_id") +FLECS_API +void ecs_add_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id_add, + ecs_id_t id_remove); + +ECS_DEPRECATED("use ecs_type_from_id") +FLECS_API +ecs_type_t ecs_type_from_entity( + ecs_world_t *world, + ecs_entity_t entity); + +ECS_DEPRECATED("use ecs_type_to_id") +FLECS_API +ecs_entity_t ecs_type_to_entity( + const ecs_world_t *world, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_type_has_id") +FLECS_API +bool ecs_type_has_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity); + +ECS_DEPRECATED("use ecs_type_has_id") +FLECS_API +bool ecs_type_owns_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned); + +ECS_DEPRECATED("use ecs_term/ecs_term_w_size") +FLECS_API +void* ecs_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column); + +#define ecs_column(it, T, column)\ + ecs_column_w_size(it, sizeof(T), column) + +ECS_DEPRECATED("no replacement") +FLECS_API +int32_t ecs_column_index_from_name( + const ecs_iter_t *it, + const char *name); + +ECS_DEPRECATED("use ecs_term_source") +FLECS_API +ecs_entity_t ecs_column_source( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_id") +FLECS_API +ecs_entity_t ecs_column_entity( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("no replacement") +FLECS_API +ecs_type_t ecs_column_type( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_size") +FLECS_API +size_t ecs_column_size( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_is_readonly") +FLECS_API +bool ecs_is_readonly( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_is_owned") +FLECS_API +bool ecs_is_owned( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_iter_column") +FLECS_API +void* ecs_table_column( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_iter_column_size") +FLECS_API +size_t ecs_table_column_size( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_iter_column_index") +FLECS_API +int32_t ecs_table_component_index( + const ecs_iter_t *it, + ecs_entity_t component); + +ECS_DEPRECATED("use ecs_set_rate") +FLECS_API +ecs_entity_t ecs_set_rate_filter( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +ecs_query_t* ecs_query_new( + ecs_world_t *world, + const char *sig); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +ecs_query_t* ecs_subquery_new( + ecs_world_t *world, + ecs_query_t *parent, + const char *sig); + +ECS_DEPRECATED("use ecs_query_deinit") +FLECS_API +void ecs_query_free( + ecs_query_t *query); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +void ecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t component, + ecs_order_by_action_t compare); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +void ecs_query_group_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t component, + ecs_group_by_action_t rank_action); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif + + + +/** + * @defgroup type_roles Type Roles + * @{ + */ + +/* Type roles are used to indicate the role of an entity in a type. If no flag + * is specified, the entity is interpreted as a regular component or tag. Flags + * are added to an entity by using a bitwise OR (|). An example: + * + * ecs_entity_t parent = ecs_new(world, 0); + * ecs_entity_t child = ecs_add_pair(world, e, EcsChildOf, parent); + * + * Type flags can also be used in type expressions, without the ECS prefix: + * + * ECS_ENTITY(world, Base, Position); + * ECS_TYPE(world, InstanceOfBase, (IsA, Base)); + */ + +/** Role bit added to roles to differentiate between roles and generations */ +#define ECS_ROLE (1ull << 63) + +/** Cases are used to switch between mutually exclusive components */ +FLECS_API extern const ecs_id_t ECS_CASE; + +/** Switches allow for fast switching between mutually exclusive components */ +FLECS_API extern const ecs_id_t ECS_SWITCH; + +/** The PAIR role indicates that the entity is a pair identifier. */ +FLECS_API extern const ecs_id_t ECS_PAIR; + +/** Enforce ownership of a component */ +FLECS_API extern const ecs_id_t ECS_OWNED; + +/** Track whether component is enabled or not */ +FLECS_API extern const ecs_id_t ECS_DISABLED; + +/** @} */ + + +/** + * @defgroup builtin_tags Builtin Tags + * @{ + */ + +/** 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 ("*"), Used in expressions to indicate wildcard matching */ +FLECS_API extern const ecs_entity_t EcsWildcard; + +/* This entity (".", "This"). Used in expressions to indicate This entity */ +FLECS_API extern const ecs_entity_t EcsThis; + +/* Can be added to relation to indicate it is transitive. */ +FLECS_API extern const ecs_entity_t EcsTransitive; + +/* Can be added to component/relation to indicate it is final. Final components/ + * relations cannot be derived from using an IsA relationship. Queries will not + * attempt to substitute a component/relationship with IsA subsets if they are + * final. */ +FLECS_API extern const ecs_entity_t EcsFinal; + +/* Can be added to relation to indicate that it should never hold data, even + * when it or the relation object is a component. */ +FLECS_API extern const ecs_entity_t EcsTag; + +/* Tag to indicate name identifier */ +FLECS_API extern const ecs_entity_t EcsName; + +/* Tag to indicate symbol identifier */ +FLECS_API extern const ecs_entity_t EcsSymbol; + +/* Used to express parent-child relations. */ +FLECS_API extern const ecs_entity_t EcsChildOf; + +/* Used to express is-a relations. An IsA relation indicates that the subject is + * a subset of the relation object. For example: + * ecs_add_pair(world, Freighter, EcsIsA, SpaceShip); + * + * Here the Freighter is considered a subset of SpaceShip, meaning that every + * entity that has Freighter also implicitly has SpaceShip. + * + * The subject of the relation (Freighter) inherits all components from any IsA + * object (SpaceShip). If SpaceShip has a component "MaxSpeed", this component + * will also appear on Freighter after adding (IsA, SpaceShip) to Freighter. + * + * The IsA relation is transitive. This means that if SpaceShip IsA Machine, + * then Freigther is also a Machine. As a result, Freighter also inherits all + * components from Machine, just as it does from SpaceShip. + * + * Queries/filters may implicitly substitute predicates, subjects and objects + * with their IsA super/subsets. This behavior can be controlled by the "set" + * member of a query term. + */ +FLECS_API extern const ecs_entity_t EcsIsA; + +/* Tag added to module entities */ +FLECS_API extern const ecs_entity_t EcsModule; + +/* Tag added to prefab entities. Any entity with this tag is automatically + * ignored by filters/queries, unless EcsPrefab is explicitly added. */ +FLECS_API extern const ecs_entity_t EcsPrefab; + +/* When this tag is added to an entity it is skipped by all queries/filters */ +FLECS_API extern const ecs_entity_t EcsDisabled; + +/* Tag added to builtin/framework entites. This tag can be used to automatically + * hide components/systems that are part of infrastructure code vs. application + * code. The tag has no functional implications. */ +FLECS_API extern const ecs_entity_t EcsHidden; + +/* Event. Triggers when an id (component, tag, pair) is added to an entity */ +FLECS_API extern const ecs_entity_t EcsOnAdd; + +/* Event. Triggers when an id (component, tag, pair) is removed from an entity */ +FLECS_API extern const ecs_entity_t EcsOnRemove; + +/* Event. Triggers when a component is set for an entity */ +FLECS_API extern const ecs_entity_t EcsOnSet; + +/* Event. Triggers when a component is unset for an entity */ +FLECS_API extern const ecs_entity_t EcsUnSet; + +/* Event. Triggers when an entity is deleted. + * Also used as relation for defining cleanup behavior, see: + * https://github.com/SanderMertens/flecs/blob/master/docs/Relations.md#relation-cleanup-properties + */ +FLECS_API extern const ecs_entity_t EcsOnDelete; + +/* Event. Triggers when a table is created. */ +// FLECS_API extern const ecs_entity_t EcsOnCreateTable; + +/* Event. Triggers when a table is deleted. */ +// FLECS_API extern const ecs_entity_t EcsOnDeleteTable; + +/* Event. Triggers when a table becomes empty (doesn't trigger on creation). */ +// FLECS_API extern const ecs_entity_t EcsOnTableEmpty; + +/* Event. Triggers when a table becomes non-empty. */ +// FLECS_API extern const ecs_entity_t EcsOnTableNonEmpty; + +/* Event. Triggers when a trigger is created. */ +// FLECS_API extern const ecs_entity_t EcsOnCreateTrigger; + +/* Event. Triggers when a trigger is deleted. */ +// FLECS_API extern const ecs_entity_t EcsOnDeleteTrigger; + +/* Event. Triggers when observable is deleted. */ +// FLECS_API extern const ecs_entity_t EcsOnDeleteObservable; + +/* Event. Triggers when lifecycle methods for a component are registered */ +// FLECS_API extern const ecs_entity_t EcsOnComponentLifecycle; + +/* Relationship used to define what should happen when an entity is deleted that + * is added to other entities. For details see: + * https://github.com/SanderMertens/flecs/blob/master/docs/Relations.md#relation-cleanup-properties + */ +FLECS_API extern const ecs_entity_t EcsOnDeleteObject; + +/* Specifies that a component/relation/object of relation should be removed when + * it is deleted. Must be combined with EcsOnDelete or EcsOnDeleteObject. */ +FLECS_API extern const ecs_entity_t EcsRemove; + +/* Specifies that entities with a component/relation/object of relation should + * be deleted when the component/relation/object of relation is deleted. Must be + * combined with EcsOnDelete or EcsOnDeleteObject. */ +FLECS_API extern const ecs_entity_t EcsDelete; + +/* Specifies that whenever a component/relation/object of relation is deleted an + * error should be thrown. Must be combined with EcsOnDelete or + * EcsOnDeleteObject. */ +FLECS_API extern const ecs_entity_t EcsThrow; + +/* System module tags */ +FLECS_API extern const ecs_entity_t EcsOnDemand; +FLECS_API extern const ecs_entity_t EcsMonitor; +FLECS_API extern const ecs_entity_t EcsDisabledIntern; +FLECS_API extern const ecs_entity_t EcsInactive; + +/* Pipeline module tags */ +FLECS_API extern const ecs_entity_t EcsPipeline; +FLECS_API extern const ecs_entity_t EcsPreFrame; +FLECS_API extern const ecs_entity_t EcsOnLoad; +FLECS_API extern const ecs_entity_t EcsPostLoad; +FLECS_API extern const ecs_entity_t EcsPreUpdate; +FLECS_API extern const ecs_entity_t EcsOnUpdate; +FLECS_API extern const ecs_entity_t EcsOnValidate; +FLECS_API extern const ecs_entity_t EcsPostUpdate; +FLECS_API extern const ecs_entity_t EcsPreStore; +FLECS_API extern const ecs_entity_t EcsOnStore; +FLECS_API extern const ecs_entity_t EcsPostFrame; + +/* Value used to quickly check if component is builtin. This is used to quickly + * filter out tables with builtin components (for example for ecs_delete) */ +#define EcsLastInternalComponentId (ecs_id(EcsSystem)) + +/* The first user-defined component starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserComponentId (32) + +/* The first user-defined entity starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserEntityId (ECS_HI_COMPONENT_ID + 128) + +/** @} */ + + +/** + * @defgroup convenience_macros Convenience Macro's + * @{ + */ + +/* Macro's rely on variadic arguments which are C99 and above */ +#ifndef FLECS_LEGACY + +/** Declare a component. + * Example: + * ECS_COMPONENT(world, Position); + */ +#ifndef ECS_COMPONENT +#define ECS_COMPONENT(world, id) \ + ecs_id_t ecs_id(id) = ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + (void)ecs_id(id); +#endif + +/** Declare an extern component variable. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DECLARE. + * + * Example: + * ECS_COMPONENT_EXTERN(Position); + */ +#ifndef ECS_COMPONENT_EXTERN +#define ECS_COMPONENT_EXTERN(id)\ + extern ecs_id_t ecs_id(id); +#endif + +/** Declare a component variable outside the scope of a function. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DEFINE. + * + * Example: + * ECS_COMPONENT_IMPL(Position); + */ +#ifndef ECS_COMPONENT_DECLARE +#define ECS_COMPONENT_DECLARE(id)\ + ecs_id_t ecs_id(id); +#endif + +/** Define a component, store in variable outside of the current scope. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DECLARE. + * + * Example: + * ECS_COMPONENT_DEFINE(world, Position); + */ +#ifndef ECS_COMPONENT_DEFINE +#define ECS_COMPONENT_DEFINE(world, id)\ + ecs_id(id)= ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + }); +#endif + +/** Declare a tag. + * Example: + * ECS_TAG(world, MyTag); + */ +#ifndef ECS_TAG +#define ECS_TAG(world, id)\ + ECS_ENTITY(world, id, 0); +#endif + +/** Declare an extern tag variable. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DECLARE. + * + * Example: + * ECS_TAG_EXTERN(Enemy); + */ +#ifndef ECS_TAG_EXTERN +#define ECS_TAG_EXTERN(id)\ + extern ecs_entity_t id; +#endif + +/** Declare a tag variable outside the scope of a function. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DEFINE. + * + * Example: + * ECS_TAG_DECLARE(Enemy); + */ +#ifndef ECS_TAG_DECLARE +#define ECS_TAG_DECLARE(id)\ + ecs_entity_t id; +#endif + +/** Define a tag, store in variable outside of the current scope. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DECLARE. + * + * Example: + * ECS_TAG_DEFINE(world, Enemy); + */ +#ifndef ECS_TAG_DEFINE +#define ECS_TAG_DEFINE(world, id)\ + id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id\ + }); +#endif + +/** Declare a constructor. + * Example: + * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); + */ +#define ECS_CTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) + +/** Declare a destructor. + * Example: + * ECS_DTOR(MyType, ptr, { free(ptr->value); }); + */ +#define ECS_DTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) + +/** Declare a copy action. + * Example: + * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + */ +#define ECS_COPY(type, dst_var, src_var, ...)\ + ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare a move action. + * Example: + * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + */ +#define ECS_MOVE(type, dst_var, src_var, ...)\ + ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare an on_set action. + * Example: + * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); + */ +#define ECS_ON_SET(type, ptr, ...)\ + ECS_ON_SET_IMPL(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 + +#endif /* FLECS_LEGACY */ + +/** @} */ + +/** + * @defgroup world_api World API + * @{ + */ + +/** Create a new world. + * A world manages all the ECS data and supporting infrastructure. Applications + * must have at least one world. Entities, component and system handles are + * local to a world and should not be shared between worlds. + * + * This operation creates a world with all builtin modules loaded. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_init(void); + +/** Same as ecs_init, but with minimal set of modules loaded. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_mini(void); + +/** Create a new world with arguments. + * Same as ecs_init, but allows passing in command line arguments. These can be + * used to dynamically enable flecs features to an application. Currently these + * arguments are not used. + * + * @return A new world object + */ +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); + +/** 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); + +/** 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 signaled. + * + * @param world The world. + */ +FLECS_API +bool ecs_should_quit( + const ecs_world_t *world); + +/** Register ctor, dtor, copy & move actions for component. + * + * @param world The world. + * @param component The component id for which to register the actions + * @param actions Type that contains the component actions. + */ +FLECS_API +void ecs_set_component_actions_w_id( + ecs_world_t *world, + ecs_id_t id, + EcsComponentLifecycle *actions); + +#ifndef FLECS_LEGACY +#define ecs_set_component_actions(world, component, ...)\ + ecs_set_component_actions_w_id(world, ecs_id(component), &(EcsComponentLifecycle)__VA_ARGS__) +#endif + +/** Set a world context. + * This operation allows an application to register custom data with a world + * that can be accessed anywhere where the application has the world object. + * + * @param world The world. + * @param ctx A pointer to a user defined structure. + */ +FLECS_API +void ecs_set_context( + ecs_world_t *world, + void *ctx); + +/** Get the world context. + * This operation retrieves a previously set world context. + * + * @param world The world. + * @return The context set with ecs_set_context. If no context was set, the + * function returns NULL. + */ +FLECS_API +void* ecs_get_context( + const ecs_world_t *world); + +/** Get world info. + * + * @param world The world. + * @return Pointer to the world info. This pointer will remain valid for as long + * as the world is valid. + */ +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. Note that this function does not allocate + * memory for components (use ecs_dim_type for that). + * + * @param world The world. + * @param entity_count The number of entities to preallocate. + */ +FLECS_API +void ecs_dim( + ecs_world_t *world, + int32_t entity_count); + +/** Set a range for issueing new entity ids. + * This function constrains the entity identifiers returned by ecs_new to the + * specified range. This operation can be used to ensure that multiple processes + * can run in the same simulation without requiring a central service that + * coordinates issueing identifiers. + * + * If id_end is set to 0, the range is infinite. If id_end is set to a non-zero + * value, it has to be larger than id_start. If id_end is set and ecs_new is + * invoked after an id is issued that is equal to id_end, the application will + * abort. + * + * @param world The world. + * @param id_start The start of the range. + * @param id_end The end of the range. + */ +FLECS_API +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end); + +/** 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); + +/** Enable world locking while in progress. + * When locking is enabled, Flecs will lock the world while in progress. This + * allows applications to interact with the world from other threads without + * running into race conditions. + * + * This is a better alternative to applications putting a lock around calls to + * ecs_progress, since ecs_progress can sleep when FPS control is enabled, + * which is time during which other threads could perform work. + * + * Locking must be enabled before applications can use the ecs_lock and + * ecs_unlock functions. Locking is turned off by default. + * + * @param world The world. + * @param enable True if locking is to be enabled. + * @result The previous value of the setting. + */ +FLECS_API +bool ecs_enable_locking( + ecs_world_t *world, + bool enable); + +/** Locks the world. + * See ecs_enable_locking for details. + * + * @param world The world. + */ +FLECS_API +void ecs_lock( + ecs_world_t *world); + +/** Unlocks the world. + * See ecs_enable_locking for details. + * + * @param world The world. + */ +FLECS_API +void ecs_unlock( + ecs_world_t *world); + +/** Wait until world becomes available. + * When a non-flecs thread needs to interact with the world, it should invoke + * this function to wait until the world becomes available (as in, it is not + * progressing the frame). Invoking this function guarantees that the thread + * will not starve. (as opposed to simply taking the world lock). + * + * An application will have to invoke ecs_end_wait after this function returns. + * + * @param world The world. + */ +FLECS_API +void ecs_begin_wait( + ecs_world_t *world); + +/** Release world after calling ecs_begin_wait. + * This operation should be invoked after invoking ecs_begin_wait, and will + * release the world back to the thread running the main loop. + * + * @param world The world. + */ +FLECS_API +void ecs_end_wait( + ecs_world_t *world); + +/** Enable or disable tracing. + * This will enable builtin tracing. For tracing to work, it will have to be + * compiled in which requires defining one of the following macro's: + * + * ECS_TRACE_0 - All tracing is disabled + * ECS_TRACE_1 - Enable tracing level 1 + * ECS_TRACE_2 - Enable tracing level 2 and below + * ECS_TRACE_3 - Enable tracing level 3 and below + * + * If no tracing level is defined and this is a debug build, ECS_TRACE_3 will + * have been automatically defined. + * + * The provided level corresponds with the tracing level. If -1 is provided as + * value, warnings are disabled. If -2 is provided, errors are disabled as well. + * + * @param level Desired tracing level. + */ +FLECS_API +void ecs_tracing_enable( + int level); + +/** Enable/disable tracing with colors. + * By default colors are enabled. + * + * @param enabled Whether to enable tracing with colors. + */ +FLECS_API +void ecs_tracing_color_enable( + bool enabled); + +/** 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, + FLECS_FLOAT fps); + +/** Get current number of threads. */ +FLECS_API +int32_t ecs_get_threads( + ecs_world_t *world); + +/** @} */ + +/** + * @defgroup creating_entities Creating Entities + * @{ + */ + +/** Create new entity id. + * This operation returns an unused entity id. + * + * @param world The world. + * @return The new entity id. + */ +FLECS_API +ecs_entity_t ecs_new_id( + ecs_world_t *world); + +/** Create new component id. + * This operation returns a new component id. Component ids are the same as + * entity ids, but can make use of the [1 .. ECS_HI_COMPONENT_ID] range. + * + * This operation does not recycle ids. + * + * @param world The world. + * @return The new component id. + */ +FLECS_API +ecs_entity_t ecs_new_component_id( + ecs_world_t *world); + +/** Create new entity. + * This operation creates a new entity with a single entity in its type. The + * entity may contain type roles. This operation recycles ids. + * + * @param world The world. + * @param entity The entity 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 a new entity. + * This operation creates a new entity with a single component in its type. This + * operation accepts variables created with ECS_COMPONENT, ECS_TYPE and ECS_TAG. + * This operation recycles ids. + * + * @param world The world. + * @param component The component. + * @return The new entity. + */ +#ifndef ecs_new +#define ecs_new(world, type) ecs_new_w_id(world, ecs_id(type)) +#endif + +/** 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); + +/** 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); + +/** Create a new type entity. + * This operation creates a new type entity, or finds an existing one. The find + * or create behavior is the same as ecs_entity_init. + * + * A type entity is an entity with the EcsType component. This component + * a pointer to an ecs_type_t, which allows for the creation of named types. + * Named types are used in a few places, such as for pipelines and filter terms + * with the EcsAndFrom or EcsOrFrom operators. + * + * When an existing type entity is found, its types are verified with the + * provided values. If the values do not match, the operation will fail. + * + * See the documentation of ecs_type_desc_t for more details. + * + * @param world The world. + * @param desc Type entity init parameters. + * @return A handle to the new or existing type, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_type_init( + ecs_world_t *world, + const ecs_type_desc_t *desc); + +/** Create N new entities. + * This operation is the same as ecs_new_w_id, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param entity The entity. + * @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); + +/** Create N new entities and initialize components. + * This operation is the same as ecs_bulk_new_w_type, but initializes components + * with the provided component array. Instead of a type the operation accepts an + * array of component identifiers (entities). The component arrays need to be + * provided in the same order as the component identifiers. + * + * @param world The world. + * @param components Array with component identifiers. + * @param count The number of entities to create. + * @param data The data arrays to initialize the components with. + * @return The first entity id of the newly created entities. + */ +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_data( + ecs_world_t *world, + int32_t count, + const ecs_ids_t *component_ids, + void *data); + +/** Create N new entities. + * This operation is the same as ecs_new, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param component The component type. + * @param count The number of entities to create. + * @return The first entity id of the newly created entities. + */ +#ifndef ecs_bulk_new +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_id(world, ecs_id(component), count) +#endif + +/** 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. + * + * @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); + +/** @} */ + +/** + * @defgroup adding_removing Adding & Removing + * @{ + */ + +/** Add an entity to an entity. + * This operation adds a single entity to the type of an entity. Type roles may + * be used in combination with the added entity. If the entity already has the + * entity, this operation will have no side effects. + * + * @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); + +/** Add a component, type or tag to an entity. + * This operation adds a type to an entity. The resulting type of the entity + * will be the union of the previous type and the provided type. If the added + * type did not have new components, this operation will have no side effects. + * + * This operation accepts variables declared by ECS_COMPONENT, ECS_TYPE and + * ECS_TAG. + * + * @param world The world. + * @param entity The entity. + * @param component The component, type or tag to add. + */ +#ifndef ecs_add +#define ecs_add(world, entity, component)\ + ecs_add_id(world, entity, ecs_id(component)) +#endif + +/** Remove an entity from an entity. + * This operation removes a single entity from the type of an entity. Type roles + * may be used in combination with the added entity. If the entity does not have + * the entity, this operation will have no side effects. + * + * @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); + +/** Remove a component, type or tag from an entity. + * This operation removes a type to an entity. The resulting type of the entity + * will be the difference of the previous type and the provided type. If the + * type did not overlap with the entity type, this operation has no side effects. + * + * This operation accepts variables declared by ECS_COMPONENT, ECS_TYPE and + * ECS_TAG. + * + * @param world The world. + * @param entity The entity. + * @param component The component, type or tag to remove. + */ +#ifndef ecs_remove +#define ecs_remove(world, entity, type)\ + ecs_remove_id(world, entity, ecs_id(type)) +#endif + +/** @} */ + + +/** + * @defgroup enabling_disabling Enabling & Disabling components. + * @{ + */ + +/** 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_component_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable); + +#define ecs_enable_component(world, entity, T, enable)\ + ecs_enable_component_w_id(world, entity, ecs_id(T), 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_component_enabled_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +#define ecs_is_component_enabled(world, entity, T)\ + ecs_is_component_enabled_w_id(world, entity, ecs_id(T)) + +/** @} */ + + +/** + * @defgroup pairs Pairs + * @{ + */ + +/** Make a pair identifier. + * This function is equivalent to using the ecs_pair macro, and is added for + * convenience to make it easier for non C/C++ bindings to work with pairs. + * + * @param relation The relation of the pair. + * @param object The object of the pair. + */ +FLECS_API +ecs_id_t ecs_make_pair( + ecs_entity_t relation, + ecs_entity_t object); + +/** This operation accepts regular entities. For passing in component identifiers + * use ecs_typeid, like this: + * + * ecs_new_w_pair(world, ecs_id(relation), object) + * + * @param world The world. + * @param relation The relation part of the pair to add. + * @param object The object part of the pair to add. + * @return The new entity. + */ +#define ecs_new_w_pair(world, relation, object)\ + ecs_new_w_id(world, ecs_pair(relation, object)) + +/** Add a pair. + * This operation adds a pair to an entity. A pair is a combination of a + * relation and an object, can can be used to store relationships between + * entities. Example: + * + * subject = Alice, relation = Likes, object = Bob + * + * This operation accepts regular entities. For passing in component identifiers + * use ecs_typeid, like this: + * + * ecs_add_pair(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity to which to add the pair. + * @param relation The relation part of the pair to add. + * @param object The object part of the pair to add. + */ +#define ecs_add_pair(world, subject, relation, object)\ + ecs_add_id(world, subject, ecs_pair(relation, object)) + +/** Remove a pair. + * This operation removes a pair from an entity. A pair is a combination of a + * relation and an object, can can be used to store relationships between + * entities. Example: + * + * subject = Alice, relation = Likes, object = Bob + * + * This operation accepts regular entities. For passing in component identifiers + * use ecs_typeid, like this: + * + * ecs_remove_pair(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity from which to remove the pair. + * @param relation The relation part of the pair to remove. + * @param object The object part of the pair to remove. + */ +#define ecs_remove_pair(world, subject, relation, object)\ + ecs_remove_id(world, subject, ecs_pair(relation, object)) + +/** Test for a pair. + * This operation tests if an entity has a pair. This operation accepts regular + * entities. For passing in component identifiers use ecs_typeid, like this: + * + * ecs_has_pair(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity from which to remove the pair. + * @param relation The relation part of the pair to remove. + * @param object The object part of the pair to remove. + */ +#define ecs_has_pair(world, subject, relation, object)\ + ecs_has_id(world, subject, ecs_pair(relation, object)) + + +#ifndef FLECS_LEGACY + +/** Set relation of pair. + * This operation sets data for a pair, where the relation determines the type. + * A pair is a combination of a relation and an object, can can be used to store + * relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the relation is not a component, + * it will fail. The object part of the pair expects a regular entity. To pass + * a component as object, use ecs_typeid like this: + * + * ecs_set_pair(world, subject, relation, ecs_id(object)) + * + * @param world The world. + * @param subject The entity on which to set the pair. + * @param relation The relation part of the pair. This must be a component. + * @param object The object part of the pair. + */ +#define ecs_set_pair(world, subject, relation, object, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(ecs_id(relation), object),\ + sizeof(relation), &(relation)__VA_ARGS__) + + +/** Set object of pair. + * This operation sets data for a pair, where the object determines the type. + * A pair is a combination of a relation and an object, can can be used to store + * relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the object is not a component, + * it will fail. The relation part of the pair expects a regular entity. To pass + * a component as relation, use ecs_typeid like this: + * + * ecs_set_pair_object(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity. + * @param relation The relation part of the pair. + * @param object The object part of the pair. This must be a component. + */ +#define ecs_set_pair_object(world, subject, relation, object, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(relation, ecs_id(object)),\ + sizeof(object), &(object)__VA_ARGS__) + +#define ecs_get_mut_pair(world, subject, relation, object, is_added)\ + (ECS_CAST(relation*, ecs_get_mut_id(world, subject,\ + ecs_pair(ecs_id(relation), object), is_added))) + +#define ecs_get_mut_pair_object(world, subject, relation, object, is_added)\ + (ECS_CAST(object*, ecs_get_mut_id(world, subject,\ + ecs_pair(relation, ecs_id(object)), is_added))) + +#define ecs_modified_pair(world, subject, relation, object)\ + ecs_modified_id(world, subject, ecs_pair(relation, object)) + +#endif + +/** Get relation of pair. + * This operation obtains the value of a pair, where the relation determines the + * type. A pair is a combination of a relation and an object, can can be used to + * store relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the relation is not a component, + * it will fail. The object part of the pair expects a regular entity. To pass + * a component as relation, use ecs_typeid like this: + * + * ecs_get_pair(world, subject, relation, ecs_id(object)) + * + * @param world The world. + * @param subject The entity. + * @param relation The relation part of the pair. Must be a component. + * @param object The object part of the pair. + */ +#define ecs_get_pair(world, subject, relation, object)\ + (ECS_CAST(relation*, ecs_get_id(world, subject,\ + ecs_pair(ecs_id(relation), object)))) + +/** Get object of pair. + * This operation obtains the value of a pair, where the object determines the + * type. A pair is a combination of a relation and an object, can can be used to + * store relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the object is not a component, + * it will fail. The relation part of the pair expects a regular entity. To pass + * a component as relation, use ecs_typeid like this: + * + * ecs_get_pair_object(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity. + * @param relation The relation part of the pair. Must be a component. + * @param object The object part of the pair. + */ +#define ecs_get_pair_object(world, subject, relation, object)\ + (ECS_CAST(object*, ecs_get_id(world, subject,\ + ecs_pair(relation, ecs_id(object))))) + +/** @} */ + + +/** + * @defgroup deleting Deleting Entities and components + * @{ + */ + +/** Clear all components. + * This operation will clear all components from an entity but will not delete + * the entity itself. This effectively prevents the entity id from being + * recycled. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity); + +/** Delete an entity. + * This operation will delete an entity and all of its components. The entity id + * will be recycled. Repeatedly calling ecs_delete without ecs_new, + * ecs_new_w_id or ecs_new_w_type will cause a memory leak as it will cause + * the list with ids that can be recycled to grow unbounded. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity); + + +/** Delete children of an entity. + * This operation deletes all children of a parent entity. If a parent has no + * children this operation has no effect. + * + * @param world The world. + * @param parent The parent entity. + */ +FLECS_API +void ecs_delete_children( + ecs_world_t *world, + ecs_entity_t parent); + +/** @} */ + + +/** + * @defgroup getting Getting 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. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + + +/** Get an immutable pointer to a component. + * Same as ecs_get_id, but accepts the typename of a component. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +#define ecs_get(world, entity, component)\ + (ECS_CAST(const component*, ecs_get_id(world, entity, ecs_id(component)))) + +/* -- Get cached pointer -- */ + +/** Get an immutable reference to a component. + * This operation is similar to ecs_get_id but it stores temporary + * information in a `ecs_ref_t` value which allows subsequent lookups to be + * faster. + * + * @param world The world. + * @param ref Pointer to a ecs_ref_t value. Must be initialized. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_ref_w_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_id_t id); + +/** Get an immutable reference to a component. + * Same as ecs_get_ref_w_id, but accepts the typename of a component. + * + * @param world The world. + * @param ref Pointer to a ecs_ref_t value. Must be initialized. + * @param entity The entity. + * @param id The component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +#define ecs_get_ref(world, ref, entity, component)\ + (ECS_CAST(const component*, ecs_get_ref_w_id(world, ref, entity, ecs_id(component)))) + +/** Get case for switch. + * This operation gets the current case for the specified switch. If the current + * switch is not set for the entity, the operation will return 0. + * + * @param world The world. + * @param e The entity. + * @param sw The switch for which to obtain the case. + * @return The current case for the specified switch. + */ +FLECS_API +ecs_entity_t ecs_get_case( + const ecs_world_t *world, + ecs_entity_t e, + ecs_entity_t sw); + +/** @} */ + + +/** + * @defgroup setting Setting Components + * @{ + */ + +/** Get a mutable pointer to a component. + * This operation is similar to ecs_get_id but it returns a mutable + * pointer. If this operation is invoked from inside a system, the entity will + * be staged and a pointer to the staged component will be returned. + * + * If the entity did not yet have the component, the component will be added by + * this operation. In this case the is_added out parameter will be set to true. + * + * @param world The world. + * @param entity The entity. + * @param id The entity id of the component to obtain. + * @param is_added Out parameter that returns true if the component was added. + * @return The component pointer. + */ +FLECS_API +void* ecs_get_mut_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added); + +/** Get a mutable pointer to a component. + * Same as ecs_get_mut_id but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param T The component type to obtain. + * @param is_added Out parameter that returns true if the component was added. + * @return The component pointer. + */ +#define ecs_get_mut(world, entity, T, is_added)\ + (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T), is_added))) + +/** Emplace a component. + * Emplace is similar to get_mut except that the component constructor is not + * invoked for the returned pointer, allowing the component to be "constructed" + * directly in the storage. + * + * Emplace can only be used if the entity does not yet have the component. If + * the entity has the component, the operation will fail. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The (uninitialized) component pointer. + */ +FLECS_API +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Emplace a component. + * Same as ecs_emplace_id but accepts a typename. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The (uninitialized) component pointer. + */ +#define ecs_emplace(world, entity, T)\ + (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) + + +/** Signal that a component has been modified. + * This operation allows an application to signal to Flecs that a component has + * been modified. As a result, OnSet systems will be invoked. + * + * This operation is commonly used together with ecs_get_mut. + * + * @param world The world. + * @param entity The entity. + * @param component The entity 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); + +/** Signal that a component has been modified. + * Same as ecs_modified_id but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param id The component that was modified. + */ +#define ecs_modified(world, entity, component)\ + ecs_modified_id(world, entity, ecs_id(component)) + +/** Set the value of a component. + * This operation allows an application to set the value of a component. The + * operation is equivalent to calling ecs_get_mut and ecs_modified. + * + * If the provided entity is 0, a new entity will be created. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to set. + * @param size The size of the pointer to the value. + * @param ptr The pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +FLECS_API +ecs_entity_t ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr); + +/** Set the value of a component. + * Same as ecs_set_id, but accepts a component typename and + * automatically determines the type size. + * + * @param world The world. + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +#define ecs_set_ptr(world, entity, component, ptr)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) + +/* Conditionally skip macro's as compound literals and variadic arguments are + * not supported in C89 */ +#ifndef FLECS_LEGACY + +/** Set the value of a component. + * Same as ecs_set_ptr, but accepts a value instead of a pointer to a value. + * + * @param world The world. + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +#define ecs_set(world, entity, component, ...)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) + +#endif + +/** @} */ + + +/** + * @defgroup singleton Singleton components + * @{ + */ + +#define ecs_singleton_get(world, comp)\ + ecs_get(world, ecs_id(comp), comp) + +#ifndef FLECS_LEGACY +#define ecs_singleton_set(world, comp, ...)\ + ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) +#endif + +#define ecs_singleton_get_mut(world, comp)\ + ecs_get_mut(world, ecs_id(comp), comp, NULL) + +#define ecs_singleton_modified(world, comp)\ + ecs_modified(world, ecs_id(comp), comp) + +/** + * @defgroup testing Testing Components + * @{ + */ + +/** Test if an entity has an entity. + * This operation returns true if the entity has the provided entity in its + * type. + * + * @param world The world. + * @param entity The entity. + * @param id The id to test for. + * @return True if the entity has the entity, false if not. + */ +FLECS_API +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Test if an entity has a component, type or tag. + * This operation returns true if the entity has the provided component, type or + * tag in its type. + * + * @param world The world. + * @param entity The entity. + * @param type The component, type or tag to test for. + * @return True if the entity has the type, false if not. + */ +#ifndef ecs_has +#define ecs_has(world, entity, type)\ + ecs_has_id(world, entity, ecs_id(type)) +#endif + +/** Test if an entity owns an entity. + * This operation is similar to ecs_has, but will return false if the entity + * does not own the entity, which is the case if the entity is defined on + * a base entity with an IsA pair. + * + * @param world The world. + * @param entity The entity. + * @param type The entity to test for. + * @return True if the entity owns the entity, false if not. + */ +#ifndef ecs_owns +#define ecs_owns(world, entity, has, owned)\ + ecs_type_has_id(world, ecs_get_type(world, entity), has, owned) +#endif + +/** @} */ + +/** + * @defgroup metadata Entity Metadata + * @{ + */ + +/** Test whether an entity is valid. + * Entities that are valid can be used with API functions. + * + * An entity is valid if it is not 0 and if it is alive. If the provided id has + * a role or a pair, the contents of the role or the pair will be checked for + * validity. + * + * is_valid will return true for ids that don't exist (alive or not alive). This + * allows for using ids that have never been created by ecs_new or similar. In + * this the function differs from ecs_is_alive, which will return false for + * entities that do not yet exist. + * + * The 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. + * An entity is alive when it has been returned by ecs_new (or similar) or if + * it is not empty (componentts have been explicitly added to the id). + * + * @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); + +/** 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 relation, the parent + * (object part of the pair) will have been stored in a 32 bit value, which + * cannot store the entity generation. This function can retrieve the identifier + * with the current generation for that id. + * + * 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 (such as a global constant or an id from a remote application). + * + * Before this operation the id must either not yet exist, or must exist with + * the same generation as the provided id. If the id has been recycled and the + * provided id does not have the same generation count, the function will fail. + * + * If the provided entity is not alive, and the provided generation count is + * equal to the current generation (which is the future generation when the id + * will be recycled) the id will become alive again. + * + * 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. + * + * This behavior ensures that an application can use ecs_ensure to track the + * lifecycle of an id without explicitly having to create it. It also protects + * against reviving an id with a generation count that was not yet due. + * + * @param world The world. + * @param entity The entity id to make alive. + */ +FLECS_API +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t e); + +/** Test whether an entity exists. + * Similar as ecs_is_alive, but ignores entity generation count. + * + * @param world The world. + * @param e 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 e); + +/** 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 +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. + */ +FLECS_API +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get the typeid of an entity. + * + * @param world The world. + * @param entity The entity. + * @return The typeid of the entity. + */ +FLECS_API +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t e); + +/** Get the name of an entity. + * This will return the name as specified in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no 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 name as specified in the EcsSymbol component. + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + */ +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 will be stored in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @param name The entity's name. + * @return The provided entity, or a new entity if 0 was provided. + */ +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 will be stored in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @param symbol The entity's symbol. + * @return The provided entity, or a new entity if 0 was provided. + */ +FLECS_API +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *symbol); + +/** Convert type role to string. + * This operation converts a type role to a string. + * + * @param world The world. + * @param entity The entity containing the type role. + * @return The type role string, or NULL if no type role is provided. + */ +FLECS_API +const char* ecs_role_str( + ecs_entity_t entity); + +/** 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. + * @param buffer The buffer in which to store the string. + * @param buffer_len The length of the provided buffer. + * @return The number of characters required to write the string. + */ +FLECS_API +size_t ecs_id_str( + const ecs_world_t *world, + ecs_id_t entity, + char *buffer, + size_t buffer_len); + +/** Get the object of a relation. + * This will return a object of the entity for the specified relation. The index + * allows for iterating through the objects, if a single entity has multiple + * objects for the same relation. + * + * If the index is larger than the total number of instances the entity has for + * the relation, the operation will return 0. + * + * @param world The world. + * @param entity The entity. + * @param rel The relation between the entity and the object. + * @param index The index of the relation instance. + * @return The object for the relation at the specified index. + */ +FLECS_API +ecs_entity_t ecs_get_object( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index); + +/** Enable or disable an 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); + +/** 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); + +/** Count entities that have a component, type or tag. + * Returns the number of entities that have the specified component, type or tag. + * + * @param world The world. + * @param type The component, type or tag. + * @return The number of entities that have the component, type or tag. + */ +#define ecs_count(world, type)\ + ecs_count_type(world, ecs_type(type)) + +/** Count entities that match a filter. + * Returns the number of entities that match the specified filter. + * + * @param world The world. + * @param type The type. + * @return The number of entities that match the specified filter. + */ +FLECS_API +int32_t ecs_count_filter( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** @} */ + + +/** + * @defgroup lookup Lookups + * @{ + */ + +/** Lookup an entity by name. + * Returns an entity that matches the specified name. Only looks for entities in + * the current scope (root if no scope is provided). + * + * @param world The world. + * @param name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + */ +FLECS_API +ecs_entity_t ecs_lookup( + const ecs_world_t *world, + const char *name); + +/** 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 name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + */ +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. + */ +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 from a path. + * Same as ecs_lookup_path_w_sep, but with defaults for the separator and + * prefix. These defaults are used when looking up identifiers in a type or + * signature expression. + * + * @param world The world. + * @param parent The entity from which to resolve the path. + * @param path The path to resolve. + * @return The entity if found, else 0. + */ +#define ecs_lookup_path(world, parent, path)\ + ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) + +/** Lookup an entity from a full path. + * Same as ecs_lookup_path, but searches from the current scope, or root scope + * if no scope is set. + * + * @param world The world. + * @param path The path to resolve. + * @return The entity if found, else 0. + */ +#define ecs_lookup_fullpath(world, path)\ + ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true) + +/** Lookup an entity by its symbol name. + * This looks up an entity by the symbol name that was provided in EcsName. The + * operation does not take into account scoping, which means it will search all + * entities that have an EcsName. + * + * This operation can be useful to resolve, for example, a type by its C + * identifier, which does not include the Flecs namespacing. + */ +FLECS_API +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *symbol, + bool lookup_as_path); + +/* Add alias for entity to global scope */ +FLECS_API +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** @} */ + + +/** + * @defgroup paths Paths + * @{ + */ + +/** 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. + * @return The relative entity path. + */ +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); + +/** Get a path identifier for an entity. + * Same as ecs_get_path_w_sep, but with default values for the separator and + * prefix. These defaults are used throughout Flecs whenever identifiers are + * used in type or signature expressions. + * + * @param world The world. + * @param parent The entity from which to create the path. + * @param child The entity to which to create the path. + * @return The relative entity path. + */ +#define ecs_get_path(world, parent, child)\ + ecs_get_path_w_sep(world, parent, child, ".", NULL) + +/** Get a full path for an entity. + * Same as ecs_get_path, but with default values for the separator and + * prefix, and the path is created from the current scope, or root if no scope + * is provided. + * + * @param world The world. + * @param child The entity to which to create the path. + * @return The entity path. + */ +#define ecs_get_fullpath(world, child)\ + ecs_get_path_w_sep(world, 0, child, ".", NULL) + +/** 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); + +/** Find or create entity from path. + * Same as ecs_new_from_path_w_sep, but with defaults for sep and prefix. + * + * @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. + * @return The entity. + */ +#define ecs_new_from_path(world, parent, path)\ + ecs_new_from_path_w_sep(world, parent, path, ".", NULL) + +/** Find or create entity from full path. + * Same as ecs_new_from_path, but entity will be created from the current scope, + * or root scope if no scope is set. + * + * @param world The world. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_new_from_fullpath(world, path)\ + ecs_new_from_path_w_sep(world, 0, path, ".", NULL) + +/** 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); + +/** Add specified path to entity. + * Same as ecs_add_from_path_w_sep, but with defaults for sep and prefix. + * + * @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. + * @return The entity. + */ +#define ecs_add_path(world, entity, parent, path)\ + ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) + +/** Add specified path to entity. + * Same as ecs_add_from_path, but entity will be created from the current scope, + * or root scope if no scope is set. + * + * @param world The world. + * @param entity The entity to which to add the path. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_add_fullpath(world, entity, path)\ + ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) + +/** @} */ + + +/** + * @defgroup scopes Scopes + * @{ + */ + +/** Does entity have children. + * + * @param world The world + * @param entity The entity + * @return True if the entity has children, false if not. + */ +FLECS_API +int32_t ecs_get_child_count( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Return a scope iterator. + * A scope iterator iterates over all the child entities of the specified + * parent. + * + * @param world The world. + * @param parent The parent entity for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_scope_iter( + ecs_world_t *world, + ecs_entity_t parent); + +/** Return a filtered scope iterator. + * Same as ecs_scope_iter, but results will be filtered. + * + * @param world The world. + * @param parent The parent entity for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_scope_iter_w_filter( + ecs_world_t *world, + ecs_entity_t parent, + ecs_filter_t *filter); + +/** Progress the scope iterator. + * This operation progresses the scope iterator to the next table. The iterator + * must have been initialized with `ecs_scope_iter`. This operation must be + * invoked at least once before interpreting the contents of the iterator. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_scope_next( + ecs_iter_t *it); + +/** 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. + */ +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 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. + * @param id The id. + * @return The previous id. + */ +FLECS_API +ecs_entity_t ecs_get_with( + 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); + +/** @} */ + + +/** + * @defgroup terms Terms + * @{ + */ + +/** Iterator for a single (component) id. + * A term iterator returns all entities (tables) that match a single (component) + * id. The search for the matching set of entities (tables) is performed in + * constant time. + * + * Currently only trivial terms are supported (see ecs_term_is_trivial). Only + * the id field of the term needs to be initialized. + * + * @param world The world. + * @param term The term. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_term_iter( + const ecs_world_t *world, + ecs_term_t *term); + +/** Progress the term iterator. + * This operation progresses the term iterator to the next table. The + * iterator must have been initialized with `ecs_term_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_term_next( + ecs_iter_t *it); + +/** Test whether term id is set. + * + * @param id The term id. + * @return True when set, false when not set. + */ +FLECS_API +bool ecs_term_id_is_set( + const ecs_term_id_t *id); + +/** 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); + +/** Test whether a term is a trivial term. + * A trivial term is a term that only contains a type id. Trivial terms must not + * have read/write annotations, relation substitutions and subjects other than + * 'This'. Examples of trivial terms are: + * - 'Position' + * - 'Position(This)' + * - '(Likes, IceCream)' + * - 'Likes(This, IceCream)' + * + * Examples of non-trivial terms are: + * - '[in] Position' + * - 'Position(MyEntity)' + * - 'Position(self|superset)' + * + * Trivial terms are useful in expressions that should just represent a list of + * components, such as when parsing the list of components to add to an entity. + * + * The term passed to this operation must be finalized. Terms returned by the + * parser are guaranteed to be finalized. + * + * @param term The term. + * @return True if term is trivial, false if it is not. + */ +FLECS_API +bool ecs_term_is_trivial( + const ecs_term_t *term); + +/** Finalize term. + * Ensure that all fields of a term are consistent and filled out. This + * operation should be invoked before using and after assigning members to, or + * parsing a term. When a term contains unresolved identifiers, this operation + * will resolve and assign the identifiers. If the term contains any identifiers + * that cannot be resolved, the operation will fail. + * + * An application generally does not need to invoke this operation as the APIs + * that use terms (such as filters, queries and triggers) will finalize terms + * when they are created. + * + * The name and expr parameters are optional, and only used for giving more + * descriptive error messages. + * + * @param world The world. + * @param name The name of the entity that uses the term (such as a system). + * @param expr The string expression of which the term is a part. + * @param term The term to finalize. + * @return Zero if success, nonzero if an error occurred. + */ +FLECS_API +int ecs_term_finalize( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term); + +/** Copy resources of a term to another term. + * This operation copies one term to another term. If the source term contains + * allocated resources (such as identifiers), they will be duplicated so that + * no memory is shared between the terms. + * + * @param dst The term to copy to. + * @param src The term to copy from. + */ +FLECS_API +ecs_term_t ecs_term_copy( + const ecs_term_t *src); + +/** Move resources of a term to another term. + * Same as copy, but moves resources from src, if src->move is set to true. If + * src->move is not set to true, this operation will do a copy. + * + * The conditional move reduces redundant allocations in scenarios where a list + * of terms is partially created with allocated resources. + * + * @param dst The term to copy to. + * @param src The term to copy from. + */ +FLECS_API +ecs_term_t ecs_term_move( + ecs_term_t *src); + +/** Free resources of term. + * This operation frees all resources (such as identifiers) of a term. The term + * object itself is not freed. + * + * @param term The term to free. + */ +FLECS_API +void ecs_term_fini( + ecs_term_t *term); + +/** 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. + */ +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); + +/** @} */ + + +/** + * @defgroup filters Filters + * @{ + */ + +/** Initialize filter + * A filter is a lightweight object that can be used to query for entities in + * a world. Filters, as opposed to queries, do not cache results. They are + * therefore slower to iterate, but are faster to create. + * + * This operation will at minimum allocate an array to hold the filter terms in + * the returned filter struct. It may allocate additional memory if the provided + * description contains a name, expression, or if the provided array of terms + * contains strings (identifier names or term names). + * + * It is possible to create a filter without allocating any memory, by setting + * the "terms" and "term_count" members directly. When doing so an application + * should not call ecs_filter_init but ecs_filter_finalize. This will ensure + * that all fields are consistent and properly filled out. + * + * @param world The world. + * @param desc Properties for the filter to create. + * @param filter_out The filter. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_filter_init( + const ecs_world_t *world, + ecs_filter_t *filter_out, + const ecs_filter_desc_t *desc); + +/** Deinitialize filter. + * Free resources associated with filter. + * + * @param filter The filter to deinitialize. + */ +FLECS_API +void ecs_filter_fini( + ecs_filter_t *filter); + +/** Finalize filter. + * When manually assigning an array of terms to the filter struct (so not when + * using ecs_filter_init), this operation should be used to ensure that all + * terms are assigned properly and all (derived) fields have been set. + * + * When ecs_filter_init is used to create the filter, this function should not + * be called. The purpose of this operation is to support creation of filters + * without allocating memory. + * + * @param filter The filter to finalize. + * @return Zero if filter is valid, non-zero if it contains errors. + * @ + */ +FLECS_API +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *filter); + +/** Convert filter to string expression. + * Convert filter terms to a string expression. The resulting expression can be + * parsed to create the same filter. + */ +FLECS_API +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** Return a filter iterator. + * A filter iterator lets an application iterate over entities that match the + * specified filter. If NULL is provided for the filter, the iterator will + * iterate all tables in the world. + * + * @param world The world. + * @param filter The filter. + * @return An iterator that can be used with ecs_filter_next. + */ +FLECS_API +ecs_iter_t ecs_filter_iter( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** Iterate tables matched by filter. + * This operation progresses the filter iterator to the next table. The + * iterator must have been initialized with `ecs_filter_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_filter_next( + ecs_iter_t *iter); + +/** Move resources of one filter to another. */ +FLECS_API +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src); + +/** Copy resources of one filter to another. */ +FLECS_API +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src); + +/** @} */ + +/** + * @defgroup queries Queries + * @{ + */ + +/** Create a query. + * This operation creates a query. Queries are used to iterate over entities + * that match a filter and are the fastest way to find and iterate over entities + * and their components. + * + * Queries should be created once, and reused multiple times. While iterating a + * query is a cheap operation, creating and deleting a query is expensive. The + * reason for this is that queries are "prematched", which means that a query + * stores state about which entities (or rather, tables) match with the query. + * Building up this state happens during query creation. + * + * Once a query is created, matching only happens when new tables are created. + * In most applications this is an infrequent process, since it only occurs when + * a new combination of components is introduced. While matching is expensive, + * it is importent to note that matching does not happen on a per-entity basis, + * but on a per-table basis. This means that the average time spent on matching + * per frame should rapidly approach zero over the lifetime of an application. + * + * A query provides direct access to the component arrays. When an application + * creates/deletes entities or adds/removes components, these arrays can shift + * component values around, or may grow in size. This can cause unexpected or + * undefined behavior to occur if these operations are performed while + * iterating. To prevent this from happening an application should either not + * perform these operations while iterating, or use deferred operations (see + * ecs_defer_begin and ecs_defer_end). + * + * Queries can be created and deleted dynamically. If a query was not deleted + * (using ecs_query_fini) before the world is deleted, it will be deleted + * automatically. + * + * @param world The world. + * @param desc A structure describing the query properties. + * @return The new query. + */ +FLECS_API +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc); + +/** Destroy a query. + * This operation destroys a query and its resources. If the query is used as + * the parent of subqueries, those subqueries will be orphaned and must be + * deinitialized as well. + * + * @param query The query. + */ +FLECS_API +void ecs_query_fini( + ecs_query_t *query); + +/** Get filter object of query. + * This operation obtains a pointer to the internally constructed filter object + * of the query and can be used to introspect the query terms. + * + * @param query The query. + */ +FLECS_API +const ecs_filter_t* ecs_query_get_filter( + ecs_query_t *query); + +/** Return a query iterator. + * A query iterator lets an application iterate over entities that match the + * specified query. If a sorting function is specified, the query will check + * whether a resort is required upon creating the iterator. + * + * Creating a query iterator is a cheap operation that does not allocate any + * resources. An application does not need to deinitialize or free a query + * iterator before it goes out of scope. + * + * To iterate the iterator, an application should use ecs_query_next to progress + * the iterator and test if it has data. + * + * Query iteration requires an outer and an inner loop. The outer loop uses + * ecs_query_next to test if new tables are available. The inner loop iterates + * the entities in the table, and is usually a for loop that uses iter.count to + * loop through the entities and component arrays. + * + * The two loops are necessary because of how data is stored internally. + * Entities are grouped by the components they have, in tables. A single query + * can (and often does) match with multiple tables. Because each table has its + * own set of arrays, an application has to reobtain pointers to those arrays + * for each matching table. + * + * @param query The query to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter( + ecs_query_t *query); + +/** Iterate over a query. + * This operation is similar to ecs_query_iter, but starts iterating from a + * specified offset, and will not iterate more than limit entities. + * + * @param query The query to iterate. + * @param offset The number of entities to skip. + * @param limit The maximum number of entities to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter_page( + ecs_query_t *query, + int32_t offset, + int32_t limit); + +/** Progress the query iterator. + * This operation progresses the query iterator to the next table. The + * iterator must have been initialized with `ecs_query_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next( + ecs_iter_t *iter); + +/** Progress the query iterator with filter. + * This operation is the same as ecs_query_next, but accepts a filter as an + * argument. Entities not matching the filter will be skipped by the iterator. + * + * @param iter The iterator. + * @param filter The filter to apply to the iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_w_filter( + ecs_iter_t *iter, + const ecs_filter_t *filter); + +/** Progress the query iterator for a worker thread. + * This operation is similar to ecs_query_next, but provides the ability to + * divide entities up across multiple worker threads. The operation accepts a + * current thread id and a total thread id, which is used to determine which + * subset of entities should be assigned to the current thread. + * + * Current should be less than total, and there should be as many as total + * threads. If there are less entities in a table than there are threads, only + * as many threads as there are entities will iterate that table. + * + * @param it The iterator. + * @param stage_current Id of current stage. + * @param stage_count Total number of stages. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t stage_current, + int32_t stage_count); + +/** 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 + * + * @param query The query. + * @return true if entities changed, otherwise false. + */ +FLECS_API +bool ecs_query_changed( + ecs_query_t *query); + +/** Returns whether query is orphaned. + * When the parent query of a subquery is deleted, it is left in an orphaned + * state. The only valid operation on an orphaned query is deleting it. Only + * subqueries can be orphaned. + * + * @param query The query. + * @return true if query is orphaned, otherwise false. + */ +FLECS_API +bool ecs_query_orphaned( + ecs_query_t *query); + +/** @} */ + + +/** + * @defgroup trigger Triggers + */ + +/** Create trigger. + * Triggers notify the application when certain events happen such as adding or + * removing components. + * + * An application can change the trigger callback or context pointer by calling + * ecs_trigger_init for an existing trigger entity, by setting the + * ecs_trigger_desc_t::entity.entity field in combination with callback and/or + * ctx. + * + * See the documentation for ecs_trigger_desc_t for more details. + * + * @param world The world. + * @param decs The trigger creation parameters. + */ +FLECS_API +ecs_entity_t ecs_trigger_init( + ecs_world_t *world, + const ecs_trigger_desc_t *desc); + +/** Get trigger context. + * This operation returns the context pointer set for the trigger. If + * the provided entity is not a trigger, the function will return NULL. + * + * @param world The world. + * @param trigger The trigger from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_trigger_ctx( + const ecs_world_t *world, + ecs_entity_t trigger); + +/** Same as ecs_get_trigger_ctx, but for binding ctx. + * The binding context is a context typically used to attach any language + * binding specific data that is needed when invoking a callback that is + * implemented in another language. + * + * @param world The world. + * @param trigger The trigger from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_trigger_binding_ctx( + const ecs_world_t *world, + ecs_entity_t trigger); + + +typedef enum ecs_payload_kind_t { + EcsPayloadNone, + EcsPayloadEntity, + EcsPayloadTable +} ecs_payload_kind_t; + +typedef struct ecs_event_desc_t { + ecs_entity_t event; + ecs_ids_t *ids; /* When NULL, notify for all ids in entity/table type */ + ecs_payload_kind_t payload_kind; + union { + ecs_entity_t entity; + struct { + ecs_table_t *table; + int32_t offset; + int32_t count; /* When 0 notify all entities starting from offset */ + } table; + } payload; + + void *param; /* Assigned to iter param member */ + + /* Observable for which to notify the triggers/observers. If NULL, the + * world will be used as observable. */ + ecs_object_t *observable; +} ecs_event_desc_t; + +/** Send event. + */ +FLECS_API +void ecs_emit( + ecs_world_t *world, + ecs_event_desc_t *desc); + +/** @} */ + + +/** + * @defgroup observer Observers + */ + +/** 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. + */ +FLECS_API +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc); + +FLECS_API +void* ecs_get_observer_ctx( + const ecs_world_t *world, + ecs_entity_t observer); + +FLECS_API +void* ecs_get_observer_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer); + +/** @} */ + + +/** + * @defgroup iterator Iterators + * @{ + */ + +/** Obtain data for a query term. + * This operation retrieves a pointer to an array of data that belongs to the + * term in the query. The index refers to the location of the term in the query, + * and starts counting from one. + * + * For example, the query "Position, Velocity" will return the Position array + * for index 1, and the Velocity array for index 2. + * + * When the specified term is not owned by the entity this function returns a + * pointer instead of an array. This happens when the source of a term is not + * the entity being iterated, such as a shared component (from a prefab), a + * component from a parent, or another entity. The ecs_term_is_owned operation + * can be used to test dynamically if a term is owned. + * + * The provided size must be either 0 or must match the size of the datatype + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_term_size. + * + * @param it The iterator. + * @param size The size of the returned array. + * @param index The index of the term in the query. + * @return A pointer to the data associated with the term. + */ +FLECS_API +void* ecs_term_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index); + +/** Same as ecs_term_w_size, but accepts a type instead of a size. */ +#define ecs_term(it, T, index)\ + ((T*)ecs_term_w_size(it, sizeof(T), index)) + +/** Obtain the component/pair id for a term. + * This operation retrieves the id for the specified query term. Typically this + * is the component id, but it can also be a pair id or a role annotated id, + * depending on the term. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return The id associated with te term. + */ +FLECS_API +ecs_id_t ecs_term_id( + const ecs_iter_t *it, + int32_t index); + +/** Obtain the source for a term. + * This operation retrieves the source of the specified term. A source is the + * entity from which the data is retrieved. If the term is owned by the iterated + * over entity/entities, the function will return id 0. + * + * This operation can be useful to retrieve, for example, the id of a parent + * entity when a component from a parent has been requested, or to retrieve the + * id from a prefab, in the case of a shared component. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return The source associated with te term. + */ +FLECS_API +ecs_entity_t ecs_term_source( + const ecs_iter_t *it, + int32_t index); + +/** Obtain the size for a term. + * This operation retrieves the size of the datatype for the term. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return The size of the datatype associated with te term. + */ +FLECS_API +size_t ecs_term_size( + const ecs_iter_t *it, + int32_t index); + +/** Test whether the term is readonly + * This operation returns whether this is a readonly term. Readonly terms are + * annotated with [in], or are added as a const type in the C++ API. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return Whether the term is readonly. + */ +FLECS_API +bool ecs_term_is_readonly( + const ecs_iter_t *it, + int32_t index); + +/** Test whether term is set. + * This function returns false for terms with the Not operator and for terms + * with the Optional operator if the matched entities (table) do not have the + * (component) id of the term. + * + * @param it The iterator. + * @param term The term. + * @return True if term is set, false if it is not set. + */ +FLECS_API +bool ecs_term_is_set( + const ecs_iter_t *it, + int32_t term); + +/** Test whether the term is owned + * This operation returns whether the term is owned by the currently iterated + * entity. This function will return false when the term is owned by another + * entity, such as a parent or a prefab. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return Whether the term is owned by the iterated over entity/entities. + */ +FLECS_API +bool ecs_term_is_owned( + const ecs_iter_t *it, + int32_t index); + +/** Get the type of the currently entities. + * This operation returns the type of the current iterated entity/entities. A + * type is a vector that contains all ids of the components that an entity has. + * + * @param it The iterator. + * @return The type of the currently iterated entity/entities. + */ +FLECS_API +ecs_type_t ecs_iter_type( + const ecs_iter_t *it); + +/** Get the table for the current entities. + * This operation returns the table of the current iterated entities + * + * @param it The iterator. + * @return The table of the currently iterated entity/entities. + */ +FLECS_API +ecs_table_t* ecs_iter_table( + const ecs_iter_t *it); + +/** Find the column index for a given id. + * This operation finds the index of a column in the current type for the + * specified id. For example, if an entity has type Position, Velocity, and the + * application requests the id for the Velocity component, this function will + * return 1. + * + * Note that the column index returned by this function starts from 0, as + * opposed to 1 for the terms. The reason for this is that the returned index + * is equivalent to using the ecs_type_get_index function, with as type the + * value returned by ecs_iter_type. + * + * This operation can be used to request columns that are not requested by a + * query. For example, a query may request Position, Velocity, but an entity + * may also have Mass. With this function the iterator can request the data for + * Mass as well, when used in combination with ecs_iter_column. + * + * @param it The iterator. + * @return The type of the currently iterated entity/entities. + */ +FLECS_API +int32_t ecs_iter_find_column( + const ecs_iter_t *it, + ecs_id_t id); + +/** Obtain data for a column index. + * This operation can be used with the id obtained from ecs_iter_find_column to + * request data from the currently iterated over entity/entities that is not + * requested by the query. + * + * The data in the returned pointer can be accessed using the same index as + * the one used to access the arrays returned by the ecs_term function. + * + * The provided size must be either 0 or must match the size of the datatype + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_iter_column_size. + * + * Note that this function can be used together with ecs_iter_type to + * dynamically iterate all data that the matched entities have. An application + * can use the ecs_vector_count function to obtain the number of elements in a + * type. All indices from 0..ecs_vector_count(type) are valid column indices. + * + * Additionally, note that this provides unprotected access to the column data. + * An iterator cannot know or prevent accessing columns that are not queried for + * and thus applications should only use this when it can be guaranteed that + * there are no other threads reading/writing the same column data. + * + * @param it The iterator. + * @param size The size of the column. + * @param index The index of the column. + * @return The data belonging to the column. + */ +FLECS_API +void* ecs_iter_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index); + +/** Same as ecs_iter_column_w_size, but accepts a type instead of a size. */ +#define ecs_iter_column(it, T, index)\ + ((T*)ecs_iter_column_w_size(it, sizeof(T), index)) + +/** Obtain size for a column index. + * This operation obtains the size for a column. The size is equal to the size + * of the datatype associated with the column. + * + * @param it The iterator. + * @param index The index of the column. + * @return The size belonging to the column. + */ +FLECS_API +size_t ecs_iter_column_size( + const ecs_iter_t *it, + int32_t index); + +/** @} */ + + +/** + * @defgroup staging Staging + * @{ + */ + +/** 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 +FLECS_FLOAT ecs_frame_begin( + ecs_world_t *world, + FLECS_FLOAT 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); + +/** Begin staging. + * When an application does not use ecs_progress to control the main loop, it + * can still use Flecs features such as the defer queue. When an application + * needs to stage changes, it needs to call this function after ecs_frame_begin. + * A call to ecs_staging_begin must be followed by a call to ecs_staging_end. + * + * When staging is enabled, modifications to entities are stored to a stage. + * This ensures that arrays are not modified while iterating. Modifications are + * merged back to the "main stage" when ecs_staging_end is invoked. + * + * While the world is in staging mode, no structural changes (add/remove/...) + * can be made to the world itself. Operations must be executed on a stage + * instead (see ecs_get_stage). + * + * This function should only be ran from the main thread. + * + * @param world The world + * @return Whether world is currently staged. + */ +FLECS_API +bool ecs_staging_begin( + ecs_world_t *world); + +/** End staging. + * Leaves staging mode. After this operation the world may be directly mutated + * again. By default this operation also merges data back into the world, unless + * automerging was disabled explicitly. + * + * This function should only be ran from the main thread. + * + * @param world The world + */ +FLECS_API +void ecs_staging_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 progress() or after staging_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 + * defer_begin and 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. + */ +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. + */ +FLECS_API +bool ecs_is_deferred( + const ecs_world_t *world); + +/** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + * + * @param world The world. + * @return true if world changed from deferred mode to non-deferred mode. + */ +FLECS_API +bool ecs_defer_end( + ecs_world_t *world); + +/** Enable/disable automerging for world or stage. + * When automerging is enabled, staged data will automatically be merged with + * the world when staging ends. This happens at the end of progress(), at a + * sync point or when staging_end() is called. + * + * Applications can exercise more control over when data from a stage is merged + * by disabling automerging. This requires an application to explicitly call + * merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this function is + * invoked on a stage, automerging is only set for that specific stage. + * + * @param world The world. + * @param automerge Whether to enable or disable automerging. + */ +FLECS_API +void ecs_set_automerge( + ecs_world_t *world, + bool automerge); + +/** 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 set_stages() 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_stages( + ecs_world_t *world, + int32_t stages); + +/** Get number of configured stages. + * Return number of stages set by ecs_set_stages. + * + * @param world The world. + * @return The number of stages used for threading. + */ +FLECS_API +int32_t ecs_get_stage_count( + const ecs_world_t *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. + * + * @param world The world. + * @return The stage id. + */ +FLECS_API +int32_t ecs_get_stage_id( + 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 dediated 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); + +/** Get actual world from world. + * @param world A pointer to a stage or the world. + * @return The world. + */ +FLECS_API +const ecs_world_t* ecs_get_world( + const ecs_world_t *world); + +/** Test whether the current world object is readonly. + * This function allows the code to test whether the currently used world object + * is readonly or whether it allows for writing. + * + * @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 *stage); + +/** Create asynchronous stage. + * An asynchronous stage can be used to asynchronously queue operations for + * later merging with the world. An asynchronous stage is similar to a regular + * stage, except that it does not allow reading from the world. + * + * Asynchronous stages are never merged automatically, and must therefore be + * manually merged with the ecs_merge function. It is not necessary to call + * defer_begin or defer_end before and after enqueuing commands, as an + * asynchronous stage unconditionally defers operations. + * + * The application must ensure that no commands are added to the stage while the + * stage is being merged. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * + * @param world The world. + * @return The stage. + */ +FLECS_API +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world); + +/** Free asynchronous stage. + * The provided stage must be an asynchronous stage. If a non-asynchronous stage + * is provided, the operation will fail. + * + * @param stage The stage to free. + */ +FLECS_API +void ecs_async_stage_free( + ecs_world_t *stage); + +/** Test whether provided stage is asynchronous. + * + * @param stage The stage. + * @return True when the stage is asynchronous, false for a regular stage or + * world. + */ +FLECS_API +bool ecs_stage_is_async( + ecs_world_t *stage); + +/** @} */ + + +/** + * @defgroup table_functions Public table operations + * @brief Low-level table functions. These functions are intended to enable the + * creation of higher-level operations. It is not recommended to use + * these operations directly in application code as they do not provide + * the same safety guarantees as the other APIs. + * @{ + */ + +/** Find or create table with specified component string. + * The provided string must be a comma-separated list of fully qualified + * component identifiers. The returned table will have the specified components. + * Two lists that are the same but specify components in a different order will + * return the same table. + * + * @param world The world. + * @param type The components. + * @return The new or existing table, or NULL if the string contains an error. + */ +FLECS_API +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *type); + +/** Find or create table from type. + * Same as ecs_table_from_str, but provides the type directly. + * + * @param world The world. + * @param type The type. + * @return The new or existing table. + */ +FLECS_API +ecs_table_t* ecs_table_from_type( + ecs_world_t *world, + ecs_type_t type); + +/** Get type for table. + * + * @param table The table. + * @return The type of the table. + */ +FLECS_API +ecs_type_t ecs_table_get_type( + const ecs_table_t *table); + +/** Insert record into table. + * This will create a new record for the table, which inserts a value for each + * component. An optional entity and record can be provided. + * + * If a non-zero entity id is provided, a record must also be provided and vice + * versa. The record must be created by the entity index. If the provided record + * is not created for the specified entity, the behavior will be undefined. + * + * If the provided record is not managed by the entity index, the behavior will + * be undefined. + * + * The returned record contains a reference to the table and the table row. The + * data pointed to by the record is guaranteed not to move unless one or more + * rows are removed from this table. A row can be removed as result of a delete, + * or by adding/removing components from an entity stored in the table. + * + * @param world The world. + * @param table The table. + * @param entity The entity. + * @param record The entity-index record for the specified entity. + * @return A record containing the table and table row. + */ +FLECS_API +ecs_record_t ecs_table_insert( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record); + +/** 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 world The world. + * @param table The table. + * @return The number of records in a table. + */ +FLECS_API +int32_t ecs_table_count( + const ecs_table_t *table); + +/** 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); + +/** 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 or unlock table. + * When a table is locked, modifications to it will trigger an assert. When the + * table is locked recursively, it will take an equal amount of unlock + * operations to actually unlock the table. + * + * 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); + +/** Returns whether table is a module or contains module contents + * Returns true for tables that have module contents. Can be used to filter out + * tables that do not contain application data. + * + * @param table The table. + * @return true if table contains module contents, false if not. + */ +FLECS_API +bool ecs_table_has_module( + ecs_table_t *table); + +/** Commit (move) entity to a table. + * This operation moves an entity from its current table to the specified + * table. This may trigger the following actions: + * - Ctor for each component in the target table + * - Move for each overlapping component + * - Dtor for each component in the source table. + * - OnAdd triggers for non-overlapping components in the target table + * - OnRemove triggers for non-overlapping components in the source table. + * + * This operation is a faster than adding/removing components individually. + * + * The application must explicitly provide the difference in components between + * tables as the added/removed parameters. This can usually be derived directly + * from the result of ecs_table_add_id and esc_table_remove_id. These arrays are + * required to properly execute OnAdd/OnRemove triggers. + * + * @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, + ecs_ids_t *added, + ecs_ids_t *removed); + +/** @} */ + +/* Optional modules */ +#ifdef FLECS_SYSTEM +/** + * @file 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 + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +/** + * @file module.h + * @brief Module addon. + * + * The module addon allows for creating and importing modules. Flecs modules + * enable applications to organize components and systems into reusable units of + * code that can easily be across projects. + */ + +#ifdef FLECS_MODULE + +#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 to load. + * @param module_name The name of the module to load. + * @param flags An integer that will be passed into the module import action. + * @param handles_out A struct with handles to the module components/systems. + * @param handles_size Size of the handles_out parameter. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name, + void *handles_out, + size_t handles_size); + +/* 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 ecs_component_desc_t *desc); + +/** Define module + */ +#define ECS_MODULE(world, id)\ + ecs_entity_t ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .name = #id,\ + .add = {EcsModule}\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + id *handles = (id*)ecs_get_mut(world, ecs_id(id), id, NULL);\ + ecs_set_scope(world, ecs_id(id));\ + (void)ecs_id(id);\ + (void)ecs_type(id);\ + (void)handles; + +/** Wrapper around ecs_import. + * This macro provides a convenient way to load a module with the world. It can + * be used like this: + * + * ECS_IMPORT(world, FlecsSystemsPhysics, 0); + * + * This macro will define entity and type handles for the component associated + * with the module. The module component will be created as a singleton. + * + * The contents of a module component are module specific, although they + * typically contain handles to the content of the module. + */ +#define ECS_IMPORT(world, id) \ + id ecs_module(id);\ + char *id##__name = ecs_module_path_from_c(#id);\ + ecs_id_t ecs_id(id) = ecs_import(\ + world, id##Import, id##__name, &ecs_module(id), sizeof(id));\ + ecs_os_free(id##__name);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + id##ImportHandles(ecs_module(id));\ + (void)ecs_id(id);\ + (void)ecs_type(id);\ + +/** Utility macro for declaring a component inside a handles type */ +#define ECS_DECLARE_COMPONENT(id)\ + ecs_id_t ecs_id(id);\ + ecs_type_t ecs_type(id) + +/** Utility macro for declaring an entity inside a handles type */ +#define ECS_DECLARE_ENTITY(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +/** Utility macro for declaring a type inside a handles type */ +#define ECS_DECLARE_TYPE(id)\ + ECS_DECLARE_ENTITY(id) + +/** Utility macro for setting a component in a module function */ +#define ECS_SET_COMPONENT(id)\ + if (handles) handles->ecs_id(id) = ecs_id(id);\ + if (handles) handles->ecs_type(id) = ecs_type(id) + +/** Utility macro for setting an entity in a module function */ +#define ECS_SET_ENTITY(id)\ + if (handles) handles->id = id; + +/** Utility macro for setting a type in a module function */ +#define ECS_SET_TYPE(id)\ + if (handles) handles->id = id;\ + if (handles) handles->ecs_type(id) = ecs_type(id); + +#define ECS_EXPORT_COMPONENT(id)\ + ECS_SET_COMPONENT(id) + +#define ECS_EXPORT_ENTITY(id)\ + ECS_SET_ENTITY(id) + +#define ECS_EXPORT_TYPE(id)\ + ECS_SET_TYPE(id) + +/** Utility macro for importing a component */ +#define ECS_IMPORT_COMPONENT(handles, id)\ + ecs_id_t ecs_id(id) = (handles).ecs_id(id); (void)ecs_id(id);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + (void)ecs_id(id);\ + (void)ecs_type(id) + +/** Utility macro for importing an entity */ +#define ECS_IMPORT_ENTITY(handles, id)\ + ecs_entity_t id = (handles).id;\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &id, 1);\ + (void)id;\ + (void)ecs_type(id) + +/** Utility macro for importing a type */ +#define ECS_IMPORT_TYPE(handles, id)\ + ecs_entity_t id = (handles).id;\ + ecs_type_t ecs_type(id) = (handles).ecs_type(id);\ + (void)id;\ + (void)ecs_type(id) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +#ifndef FLECS_SYSTEMS_H +#define FLECS_SYSTEMS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Components +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +extern ecs_type_t + ecs_type(EcsSystem), + ecs_type(EcsTickSource); + +/* Component used to provide a tick source to systems */ +typedef struct EcsTickSource { + bool tick; /* True if providing tick */ + FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ +} EcsTickSource; + +//////////////////////////////////////////////////////////////////////////////// +//// Systems API +//////////////////////////////////////////////////////////////////////////////// + +/** System status change callback */ +typedef enum ecs_system_status_t { + EcsSystemStatusNone = 0, + EcsSystemEnabled, + EcsSystemDisabled, + EcsSystemActivated, + EcsSystemDeactivated +} ecs_system_status_t; + +/** System status action. + * The status action is invoked whenever a system is enabled or disabled. Note + * that a system may be enabled but may not actually match any entities. In this + * case the system is enabled but not _active_. + * + * In addition to communicating the enabled / disabled status, the action also + * communicates changes in the activation status of the system. A system becomes + * active when it has one or more matching entities, and becomes inactive when + * it no longer matches any entities. + * + * A system switches between enabled and disabled when an application invokes the + * ecs_enable operation with a state different from the state of the system, for + * example the system is disabled, and ecs_enable is invoked with enabled: true. + * + * Additionally a system may switch between enabled and disabled when it is an + * EcsOnDemand system, and interest is generated or lost for one of its [out] + * columns. + * + * @param world The world. + * @param system The system for which to set the action. + * @param action The action. + * @param ctx Context that will be passed to the action when invoked. + */ +typedef void (*ecs_system_status_action_t)( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_t status, + void *ctx); + +/* Use with ecs_system_init */ +typedef struct ecs_system_desc_t { + /* System entity creation parameters */ + ecs_entity_desc_t entity; + + /* System query parameters */ + ecs_query_desc_t query; + + /* System callback, invoked when system is ran */ + ecs_iter_action_t callback; + + /* System status callback, invoked when system status changes */ + ecs_system_status_action_t status_callback; + + /* Associate with entity */ + ecs_entity_t self; + + /* Context to be passed to callback (as ecs_iter_t::param) */ + void *ctx; + + /* Context to be passed to system status callback */ + void *status_ctx; + + /* Binding context, for when system is implemented in other language */ + void *binding_ctx; + + /* Functions that are invoked during system cleanup to free context data. + * When set, functions are called unconditionally, even when the ctx + * pointers are NULL. */ + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t status_ctx_free; + ecs_ctx_free_t binding_ctx_free; + + /* Interval in seconds at which the system should run */ + FLECS_FLOAT interval; + + /* Rate at which the system should run */ + int32_t rate; + + /* External tick soutce that determines when system ticks */ + ecs_entity_t tick_source; +} 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); + +#ifndef FLECS_LEGACY +#define ECS_SYSTEM(world, id, kind, ...) \ + ecs_iter_action_t ecs_iter_action(id) = id;\ + ecs_entity_t id = ecs_system_init(world, &(ecs_system_desc_t){\ + .entity = { .name = #id, .add = {kind} },\ + .query.filter.expr = #__VA_ARGS__,\ + .callback = ecs_iter_action(id)\ + });\ + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);\ + (void)ecs_iter_action(id);\ + (void)id; +#endif + +/* Deprecated, use ecs_trigger_init */ +#define ECS_TRIGGER(world, trigger_name, kind, component) \ + ecs_entity_t __F##trigger_name = ecs_trigger_init(world, &(ecs_trigger_desc_t){\ + .entity.name = #trigger_name,\ + .callback = trigger_name,\ + .expr = #component,\ + .events = {kind},\ + });\ + ecs_entity_t trigger_name = __F##trigger_name;\ + ecs_assert(trigger_name != 0, ECS_INVALID_PARAMETER, NULL);\ + (void)__F##trigger_name;\ + (void)trigger_name; + +/** 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 prematched 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 + * cominbation 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, + FLECS_FLOAT 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, + FLECS_FLOAT delta_time, + void *param); + +/** Run system with offset/limit and type filter. + * This operation is the same as ecs_run, but filters the entities that will be + * iterated by the system. + * + * Entities can be filtered in two ways. Offset and limit control the range of + * entities that is iterated over. The range is applied to all entities matched + * with the system, thus may cover multiple archetypes. + * + * The type filter controls which entity types the system will evaluate. Only + * types that contain all components in the type filter will be iterated over. A + * type filter is only evaluated once per table, which makes filtering cheap if + * the number of entities is large and the number of tables is small, but not as + * cheap as filtering in the system signature. + * + * @param world The world. + * @param system The system to invoke. + * @param delta_time: The time passed since the last system invocation. + * @param filter A component or type to filter matched entities. + * @param param A user-defined parameter to pass to the system. + * @return handle to last evaluated entity if system was interrupted. + */ +FLECS_API +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param); + +/** Get the query object for a system. + * Systems use queries under the hood. This enables an application to get access + * to the underlying query object of a system. This can be useful when, for + * example, an application needs to enable sorting for a system. + * + * @param world The world. + * @param system The system from which to obtain the query. + * @return The query. + */ +FLECS_API +ecs_query_t* ecs_get_system_query( + const ecs_world_t *world, + ecs_entity_t system); + +/** Get system context. + * This operation returns the context pointer set for the system. If + * the provided entity is not a system, the function will return NULL. + * + * @param world The world. + * @param system The system from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_system_ctx( + const ecs_world_t *world, + ecs_entity_t system); + +/** Get system binding context. + * The binding context is a context typically used to attach any language + * binding specific data that is needed when invoking a callback that is + * implemented in another language. + * + * @param world The world. + * @param system The system from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_system_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system); + + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Pipeline component is empty: components and tags in module are static */ +typedef struct FlecsSystem { + int32_t dummy; +} FlecsSystem; + +FLECS_API +void FlecsSystemImport( + ecs_world_t *world); + +#define FlecsSystemImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_PIPELINE +/** + * @file 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 + +#ifndef FLECS_SYSTEM +#define FLECS_SYSTEM +#endif + + +#ifndef FLECS_PIPELINE_H +#define FLECS_PIPELINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef FLECS_LEGACY +#define ECS_PIPELINE(world, id, ...) \ + ecs_entity_t id = ecs_type_init(world, &(ecs_type_desc_t){\ + .entity = {\ + .name = #id,\ + .add = {EcsPipeline}\ + },\ + .ids_expr = #__VA_ARGS__\ + }); +#endif + +/** 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. + * @param pipeline The pipeline to set. + */ +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, + FLECS_FLOAT 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, + FLECS_FLOAT 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. + * + * Note: calling this function from an application currently only works in + * single threaded applications with a single stage. + * + * @param world The world. + * @param pipeline The pipeline to run. + */ +FLECS_API +void ecs_pipeline_run( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time); + +/** Deactivate systems that are not matched with tables. + * By default Flecs deactivates systems that are not matched with any tables. + * However, once a system has been matched with a table it remains activated, to + * prevent systems from continuously becoming active and inactive. + * + * To re-deactivate systems, an application can invoke this function, which will + * deactivate all systems that are not matched with any tables. + * + * @param world The world. + */ +FLECS_API +void ecs_deactivate_systems( + ecs_world_t *world); + + +//////////////////////////////////////////////////////////////////////////////// +//// 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. */ +FLECS_API +void ecs_set_threads( + ecs_world_t *world, + int32_t threads); + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Pipeline component is empty: components and tags in module are static */ +typedef struct FlecsPipeline { + int32_t dummy; +} FlecsPipeline; + +FLECS_API +void FlecsPipelineImport( + ecs_world_t *world); + +#define FlecsPipelineImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_TIMER +/** + * @file timer.h + * @brief Timer module. + * + * Timers can be used to trigger actions at periodic or one-shot intervals. They + * are typically used together with systems and pipelines. + */ + +#ifdef FLECS_TIMER + +#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 + + +//////////////////////////////////////////////////////////////////////////////// +//// Components +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +extern ecs_type_t + ecs_type(EcsTimer), + ecs_type(EcsRateFilter); + +/** Component used for one shot/interval timer functionality */ +typedef struct EcsTimer { + FLECS_FLOAT timeout; /* Timer timeout period */ + FLECS_FLOAT time; /* Incrementing time value */ + int32_t fired_count; /* Number of times ticked */ + bool active; /* Is the timer active or not */ + bool single_shot; /* Is this a single shot timer */ +} EcsTimer; + +/* Apply a rate filter to a tick source */ +typedef struct EcsRateFilter { + ecs_entity_t src; /* Source of the rate filter */ + int32_t rate; /* Rate of the rate filter */ + int32_t tick_count; /* Number of times the rate filter ticked */ + FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ +} EcsRateFilter; + + +//////////////////////////////////////////////////////////////////////////////// +//// Timer API +//////////////////////////////////////////////////////////////////////////////// + +/** Set timer timeout. + * This operation executes any systems associated with the timer after the + * specified timeout value. If the entity contains an existing timer, the + * timeout value will be reset. The timer can be started and stopped with + * ecs_start_timer and ecs_stop_timer. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be be a tick source after this operation. Tick + * 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, + FLECS_FLOAT 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 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 +FLECS_FLOAT ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t tick_source); + +/** Set timer interval. + * This operation will continously invoke systems associated with the timer + * after the interval period expires. If the entity contains an existing timer, + * the interval value will be reset. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be be a tick source after this operation. Tick + * 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, + FLECS_FLOAT 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 +FLECS_FLOAT ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t tick_source); + +/** Start timer. + * This operation resets the timer and starts it with the specified timeout. The + * entity must have the EcsTimer component (added by ecs_set_timeout and + * ecs_set_interval). If the entity does not have the EcsTimer component this + * operation will assert. + * + * @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. The entity must have the + * EcsTimer component or this operation will assert. + * + * @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); + +/** 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 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 timer The timer 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 +//////////////////////////////////////////////////////////////////////////////// + +/* Timers module component */ +typedef struct FlecsTimer { + int32_t dummy; +} FlecsTimer; + +FLECS_API +void FlecsTimerImport( + ecs_world_t *world); + +#define FlecsTimerImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif + +/* Optional addons */ +#ifdef FLECS_BULK +/** + * @file bulk.h + * @brief Bulk operations operate on all entities that match a provided filter. + */ + +#ifdef FLECS_BULK + +#ifndef FLECS_BULK_H +#define FLECS_BULK_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Add an entity to entities matching a filter. + * This operation is the same as ecs_add_id, but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param entity_add The entity to add. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_entity( + ecs_world_t *world, + ecs_entity_t entity_add, + const ecs_filter_t *filter); + +/** Add a type to entities matching a filter. + * This operation is the same as ecs_add_type but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The type to add. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_type( + ecs_world_t *world, + ecs_type_t type, + const ecs_filter_t *filter); + +/** Add a component / type / tag to entities matching a filter. + * This operation is the same as ecs_add but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The component, type or tag to add. + * @param filter The filter. + */ +#define ecs_bulk_add(world, type, filter)\ + ecs_bulk_add_type(world, ecs_type(type), filter) + +/** Removes an entity from entities matching a filter. + * This operation is the same as ecs_remove_id, but is applied to all + * entities that match the provided filter. + * + * @param world The world. + * @param entity_remove The entity to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_remove_entity( + ecs_world_t *world, + ecs_entity_t entity_remove, + const ecs_filter_t *filter); + +/** Remove a type from entities matching a filter. + * This operation is the same as ecs_remove_type but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The type to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_remove_type( + ecs_world_t *world, + ecs_type_t type, + const ecs_filter_t *filter); + +/** Add a component / type / tag to entities matching a filter. + * This operation is the same as ecs_remove but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The component, type or tag to remove. + * @param filter The filter. + */ +#define ecs_bulk_remove(world, type, filter)\ + ecs_bulk_remove_type(world, ecs_type(type), filter) + +/** Add / remove type from entities matching a filter. + * Combination of ecs_bulk_add_type and ecs_bulk_remove_type. + * + * @param world The world. + * @param to_add The type to add. + * @param to_remove The type to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_remove_type( + ecs_world_t *world, + ecs_type_t to_add, + ecs_type_t to_remove, + const ecs_filter_t *filter); + +/** Add / remove component, type or tag from entities matching a filter. + * Combination of ecs_bulk_add and ecs_bulk_remove. + * + * @param world The world. + * @param to_add The component, type or tag to add. + * @param to_remove The component, type or tag to remove. + * @param filter The filter. + */ +#define ecs_bulk_add_remove(world, to_add, to_remove, filter)\ + ecs_bulk_add_remove_type(world, ecs_type(to_add), ecs_type(to_remove), filter) + +/** Delete entities matching a filter. + * This operation is the same as ecs_delete, but applies to all entities that + * match a filter. + * + * @param world The world. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_MODULE +#endif +#ifdef FLECS_PLECS +/** + * @file pecs.h + * @brief Plecs addon. + * + * Plecs is a small data definition language for instantiating entities that + * reuses the existing flecs query parser. The following examples illustrate + * how a plecs snippet translates to regular flecs operations: + * + * Plecs: + * Entity + * C code: + * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * + * Plecs: + * Position(Entity) + * C code: + * ecs_entity_t Position = ecs_set_name(world, 0, "Position"); + * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * ecs_add_id(world, Entity, Position); + * + * Plecs: + * Likes(Entity, Apples) + * C code: + * ecs_entity_t Likes = ecs_set_name(world, 0, "Likes"); + * ecs_entity_t Apples = ecs_set_name(world, 0, "Apples"); + * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * ecs_add_pair(world, Entity, Likes, Apples); + * + * A plecs string may contain multiple statements, separated by a newline: + * Likes(Entity, Apples) + * Likes(Entity, Pears) + * Likes(Entity, Bananas) + */ + +#ifdef FLECS_PLECS + +#define FLECS_PARSER + +#ifndef FLECS_PLECS_H +#define FLECS_PLECS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Parse plecs string. + * This parses a plecs string and instantiates the entities in the world. + * + * @param world The world. + * @param name The script name (typically the file). + * @param str The plecs string. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *str); + +/** Parse plecs file. + * This parses a plecs file and instantiates the entities in the world. This + * operation is equivalent to loading the file contents and passing it to + * ecs_plecs_from_str. + * + * @param world The world. + * @param file The plecs file name. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_plecs_from_file( + ecs_world_t *world, + const char *filename); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_PARSER +/** + * @file parser.h + * @brief Parser addon. + * + * The parser addon parses string expressions into lists of terms, and can be + * used to construct filters, queries and types. + */ + +#ifdef FLECS_PARSER + +#ifndef FLECS_PARSER_H +#define FLECS_PARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Parse term in expression. + * This operation parses a single term in an expression and returns a pointer + * to the next term expression. + * + * If the returned pointer points to the 0-terminator, the expression is fully + * parsed. The function would typically be called in a while loop: + * + * const char *ptr = expr; + * while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { } + * + * The operation does not attempt to find entity ids from the names in the + * expression. Use the ecs_term_resolve_ids function to resolve the identifiers + * in the parsed term. + * + * The returned term will in most cases contain allocated resources, which + * should freed (or used) by the application. To free the resources for a term, + * use the ecs_term_free function. + * + * The parser accepts expressions in the legacy string format. + * + * @param world The world. + * @param name The name of the expression (optional, improves error logs) + * @param expr The expression to parse (optional, improves error logs) + * @param ptr The pointer to the current term (must be in expr). + * @param term_out Out parameter for the term. + * @return pointer to next term if successful, NULL if failed. + */ +FLECS_API +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term_out); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // FLECS_PARSER_H + +#endif // FLECS_PARSER +#endif +#ifdef FLECS_QUEUE +/** + * @file queue.h + * @brief Queue datastructure. + * + * The queue data structure implements a fixed-size ringbuffer. It is not used + * by the flecs core, but is used by flecs-hub modules. + */ + +#ifdef FLECS_QUEUE + +#ifndef FLECS_QUEUE_H_ +#define FLECS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_queue_t ecs_queue_t; + +FLECS_API +ecs_queue_t* _ecs_queue_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_queue_new(T, elem_count)\ + _ecs_queue_new(ECS_VECTOR_T(T), elem_count) + +FLECS_API +ecs_queue_t* _ecs_queue_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array); + +#define ecs_queue_from_array(T, elem_count, array)\ + _ecs_queue_from_array(ECS_VECTOR_T(T), elem_count, array) + +FLECS_API +void* _ecs_queue_push( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_queue_push(queue, T)\ + (T*)_ecs_queue_push(queue, ECS_VECTOR_T(T)) + +FLECS_API +void* _ecs_queue_get( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_queue_get(queue, T, index)\ + (T*)_ecs_queue_get(queue, ECS_VECTOR_T(T), index) + +#define ecs_queue_get_t(vector, size, alignment, index) \ + _ecs_queue_get(vector, ECS_VECTOR_U(size, alignment), index) + +FLECS_API +void* _ecs_queue_last( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_queue_last(queue, T)\ + (T*)_ecs_queue_last(queue, ECS_VECTOR_T(T)) + +FLECS_API +int32_t ecs_queue_index( + ecs_queue_t *queue); + +FLECS_API +int32_t ecs_queue_count( + ecs_queue_t *queue); + +FLECS_API +void ecs_queue_free( + ecs_queue_t *queue); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_SNAPSHOT +/** + * @file snapshot.h + * @brief Snapshot addon. + * + * A snapshot records the state of a world in a way so that it can be restored + * later. Snapshots work with POD components and non-POD components, provided + * that the appropriate lifecycle actions are registered for non-POD components. + * + * A snapshot is tightly coupled to a world. It is not possible to restore a + * snapshot from world A into world B. + */ + +#ifdef FLECS_SNAPSHOT + +#ifndef FLECS_SNAPSHOT_H +#define FLECS_SNAPSHOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** A snapshot stores the state of a world in a particular point in time. */ +typedef struct ecs_snapshot_t ecs_snapshot_t; + +/** Create a snapshot. + * This operation makes a copy of all component in the world that matches the + * specified filter. + * + * @param world The world to snapshot. + * @param return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *world); + +/** Create a filtered snapshot. + * This operation is the same as ecs_snapshot_take, but accepts an iterator so + * an application can control what is stored by the snapshot. + * + * @param iter An iterator to the data to be stored by the snapshot. + * @param next A function pointer to the next operation for the iterator. + * @param return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t action); + +/** Restore a snapshot. + * This operation restores the world to the state it was in when the specified + * snapshot was taken. A snapshot can only be used once for restoring, as its + * data replaces the data that is currently in the world. + * This operation also resets the last issued entity handle, so any calls to + * ecs_new may return entity ids that have been issued before restoring the + * snapshot. + * + * The world in which the snapshot is restored must be the same as the world in + * which the snapshot is taken. + * + * @param world The world to restore the snapshot to. + * @param snapshot The snapshot to restore. + */ +FLECS_API +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot); + +/** Obtain iterator to snapshot data. + * + * @param snapshot The snapshot to iterate over. + * @return Iterator to snapshot data. */ +FLECS_API +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot, + const ecs_filter_t *filter); + +/** Progress snapshot iterator. + * + * @param iter The snapshot iterator. + * @return True if more data is available, otherwise false. + */ +FLECS_API +bool ecs_snapshot_next( + ecs_iter_t *iter); + + +/** Free snapshot resources. + * This frees resources associated with a snapshot without restoring it. + * + * @param world The world. + * @param snapshot The snapshot to free. + */ +FLECS_API +void ecs_snapshot_free( + ecs_snapshot_t *snapshot); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_DIRECT_ACCESS +/** + * @file direct_access.h + * @brief Low-level access to underlying data structures for best performance. + * + * This API allows for low-level direct access to tables and their columns. The + * APIs primary intent is to provide fast primitives for new operations. It is + * not recommended to use the API directly in application code, as invoking the + * API in an incorrect way can lead to a corrupted datastore. + */ + +#ifdef FLECS_DIRECT_ACCESS + +#ifndef FLECS_DIRECT_ACCESS_H_ +#define FLECS_DIRECT_ACCESS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Find the index of a column in a table. + * Table columns are stored in the order of their respective component ids. As + * this is not trivial for an application to deduce, this operation returns the + * index of a column in a table for a given component. This index can be used + * in other table operations to identify a column. + * + * The returned index is determined separately for each table. Indices obtained + * for one table should not be used for another table. + * + * @param table The table. + * @param component The component for which to retrieve the column index. + * @return The column index, or -1 if the table does not have the component. + */ +FLECS_API +int32_t ecs_table_find_column( + const ecs_table_t *table, + ecs_entity_t component); + +/** Get table column. + * This operation returns the pointer to a column array. A column contains all + * the data for a component for the provided table in a contiguous array. + * + * The returned pointer is not stable, and may change when a table needs to + * resize its arrays, for example in order to accomodate for more records. + * + * @param table The table. + * @param column The column index. + * @return Vector that contains the column array. + */ +FLECS_API +ecs_vector_t* ecs_table_get_column( + const ecs_table_t *table, + int32_t column); + +/** Set table column. + * This operation enables an application to set a component column for a table. + * After the operation the column is owned by the table. Any operations that + * change the column after this operation can cause undefined behavior. + * + * Care must be taken that all columns in a table have the same number of + * elements. If one column has less elements than another, the behavior is + * undefined. The operation will not check if the assigned column is of the same + * size as other columns, as this would prevent an application from assigning + * a set of different columns to a table of a different size. + * + * Setting a column will not delete the previous column. It is the + * responsibility of the application to ensure that the old column is deleted + * properly (using ecs_table_delete_column). + * + * The provided vector must have the same element size and alignment as the + * target column. If the size and/or alignment do not match, the behavior will + * be undefined. In debug mode the operation may assert. + * + * If the provided vector is NULL, the table will ensure that a vector is + * created for the provided column. If a vector exists that is not of the + * same size as the entities vector, it will be resized to match. + * + * @param world The world. + * @param table The table. + * @param column The column index. + * @param vector The column data to assing. + */ +FLECS_API +ecs_vector_t* ecs_table_set_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector); + +/** Get the vector containing entity ids for the table. + * This operation obtains the vector with entity ids for the current table. Each + * entity id is associated with one record, and ids are stored in the same order + * as the table records. The element type of the vector is ecs_entity_t. + * + * @param table The table. + * @return The vector containing the table's entities. + */ +FLECS_API +ecs_vector_t* ecs_table_get_entities( + const ecs_table_t *table); + +/** Get the vector containing pointers to entity records. + * A table stores cached pointers to entity records for fast access. This + * operation provides direct access to the vector. The element type of the + * vector is ecs_record_t*. + * + * @param table The table. + * @return The vector containing the entity records. + */ +FLECS_API +ecs_vector_t* ecs_table_get_records( + const ecs_table_t *table); + +/** Clear records. + * This operation clears records for a world so that they no longer point to a + * table. This is useful to ensure that a world is left in a consistent state + * after moving data to destination world. + * + * @param records The vector with record pointers + */ +FLECS_API +void ecs_records_clear( + ecs_vector_t *records); + +/** Initialize records. + * This operation ensures entity records are updated to the provided table. + * + * @param world The world. + * @param entities The vector with entity identifiers. + * @param records The vector with record pointers. + * @param table The table in which the entities are stored. + */ +FLECS_API +void ecs_records_update( + ecs_world_t *world, + ecs_vector_t *entities, + ecs_vector_t *records, + ecs_table_t *table); + +/** Set the vector containing entity ids for the table. + * This operation sets the vector with entity ids for a table. In addition the + * operation also requires setting a vector with pointers to records. The + * record pointers in the vector need to be managed by the entity index. If they + * are not, this can cause undefined behavior. + * + * The provided vectors must have the same number of elements as the number of + * records in the table. If the element count is not the same, this causes + * undefined behavior. + * + * A table must have an entity and record vector, even if the table does not + * contain entities. For each record that is not an entity, the entity vector + * should contain 0, and the record vector should contain NULL. + * + * @param table The table. + * @param entities The entity vector. + * @param records The record vector. + */ +FLECS_API +void ecs_table_set_entities( + ecs_table_t *table, + ecs_vector_t *entities, + ecs_vector_t *records); + +/** Delete a column. + * This operation frees the memory of a table column and will invoke the + * component destructor if registered. + * + * The provided vector does not need to be the same as the vector in the table. + * The reason the table must be provided is so that the operation can retrieve + * the correct destructor for the component. If the component does not have a + * destructor, an application can alternatively delete the vector directly. + * + * If the specified vector is NULL, the column of the table will be removed and + * the table will be updated to no longer point at the column. If an explicit + * column is provided, the table is not modified. If a column is deleted that is + * still being pointed to by a table, behavior is undefined. It is the + * responsibility of the application to ensure that a table no longer points to + * a deleted column, by using ecs_table_set_column. + * + * Simultaneously, if this operation is used to delete a table column, the + * application should make sure that if the table contains other columns, they + * are either also deleted, or that the deleted column is replaced by a column + * of the same size. Note that this also goes for the entity and record vectors, + * they should have the same number of elements as the other columns. + * + * The vector must be of the same component as the specified column. If the + * vector is not of the same component, behavior will be undefined. In debug + * mode the API may assert, though it may not always be able to detect a + * mismatching vector/column. + * + * After this operation the vector should no longer be used by the application. + * + * @param table The table. + * @param column The column index. + * @param vector The column vector to delete. + */ +FLECS_API +void ecs_table_delete_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector); + +/** Find a record for a given entity. + * This operation finds an existing record in the entity index for a given + * entity. The returned pointer is stable for the lifecycle of the world and can + * be used as argument for the ecs_record_update operation. + * + * The returned record (if found) points to the adminstration that relates an + * entity id to a table. Updating the value of the returned record will cause + * operations like ecs_get and ecs_has to look in the updated table. + * + * Updating this record to a table in which the entity is not stored causes + * undefined behavior. + * + * When the entity has never been created or is not alive this operation will + * return NULL. + * + * @param world The world. + * @param entity The entity. + * @return The record that belongs to the entity, or NULL if not found. + */ +FLECS_API +ecs_record_t* ecs_record_find( + ecs_world_t *world, + ecs_entity_t entity); + +/** Same as ecs_record_find, but creates record if it doesn't exist. + * If an entity id has not been created with ecs_new_*, this function can be + * used to ensure that a record exists for an entity id. If the provided id + * already exists in the world, the operation will return the existing record. + * + * @param world The world. + * @param entity The entity for which to retrieve the record. + * @return The (new or existing) record that belongs to the entity. + */ +FLECS_API +ecs_record_t* ecs_record_ensure( + ecs_world_t *world, + ecs_entity_t entity); + +/** Get value from record. + * This operation gets a component value from a record. The provided column + * index must match the table of the record. + * + * @param r The record. + * @param column The column index of the component to get. + */ +FLECS_API +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t size); + +/** Copy value to a component for a record. + * This operation sets the component value of a single component for a record. + * If the component type has a copy action it will be used, otherwise the value + * be memcpyd into the component array. + * + * The provided record does not need to be managed by the entity index but does + * need to point to a valid record in the table. If the provided index is + * outside of the range indicating the number of records in the table, behavior + * is undefined. In debug mode it will cause the operation to assert. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to copy. + */ +FLECS_API +void ecs_record_copy_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + const void *value, + int32_t count); + +/** Memcpy value to a component for a record. + * Same as ecs_record_copy_to, except that this operation will always use + * memcpy. This operation should only be used for components that can be safely + * memcpyd. If the operation is used for a component that has a copy or move + * action, the behavior is undefined. In debug mode the operation may assert. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to move. + */ +FLECS_API +void ecs_record_copy_pod_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + const void *value, + int32_t count); + +/** Move value to a component for a record. + * Same as ecs_record_copy_to, except that it uses the move action. If the + * component has no move action the value will be memcpyd into the component + * array. After this operation the application can no longer assume that the + * value passed into the function is valid. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to move. + */ +FLECS_API +void ecs_record_move_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + void *value, + int32_t count); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif +#ifdef FLECS_STATS +/** + * @file stats.h + * @brief Statistics addon. + * + * The statistics addon enables an application to obtain detailed metrics about + * the storage, systems and operations of a world. + */ + +#ifdef FLECS_STATS + +#ifndef FLECS_STATS_H +#define FLECS_STATS_H + +#ifdef FLECS_SYSTEM +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_STAT_WINDOW (60) + +/** Simple value that indicates current state */ +typedef struct ecs_gauge_t { + float avg[ECS_STAT_WINDOW]; + float min[ECS_STAT_WINDOW]; + float max[ECS_STAT_WINDOW]; +} ecs_gauge_t; + +/* Monotonically increasing counter */ +typedef struct ecs_counter_t { + ecs_gauge_t rate; /**< Keep track of deltas too */ + float value[ECS_STAT_WINDOW]; +} ecs_counter_t; + +typedef struct ecs_world_stats_t { + /* Allows struct to be initialized with {0} */ + int32_t dummy_; + + ecs_gauge_t entity_count; /**< Number of entities */ + ecs_gauge_t component_count; /**< Number of components */ + ecs_gauge_t query_count; /**< Number of queries */ + ecs_gauge_t system_count; /**< Number of systems */ + ecs_gauge_t table_count; /**< Number of tables */ + ecs_gauge_t empty_table_count; /**< Number of empty tables */ + ecs_gauge_t singleton_table_count; /**< Number of singleton tables. Singleton tables are tables with just a single entity that contains itself */ + ecs_gauge_t matched_entity_count; /**< Number of entities matched by queries */ + ecs_gauge_t matched_table_count; /**< Number of tables matched by queries */ + + /* Deferred operations */ + ecs_counter_t new_count; + ecs_counter_t bulk_new_count; + ecs_counter_t delete_count; + ecs_counter_t clear_count; + ecs_counter_t add_count; + ecs_counter_t remove_count; + ecs_counter_t set_count; + ecs_counter_t discard_count; + + /* Timing */ + ecs_counter_t world_time_total_raw; /**< Actual time passed since simulation start (first time progress() is called) */ + ecs_counter_t world_time_total; /**< Simulation time passed since simulation start. Takes into account time scaling */ + ecs_counter_t frame_time_total; /**< Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_counter_t system_time_total; /**< Time spent on processing systems. */ + ecs_counter_t merge_time_total; /**< Time spent on merging deferred actions. */ + ecs_gauge_t fps; /**< Frames per second. */ + ecs_gauge_t delta_time; /**< Delta_time. */ + + /* Frame data */ + ecs_counter_t frame_count_total; /**< Number of frames processed. */ + ecs_counter_t merge_count_total; /**< Number of merges executed. */ + ecs_counter_t pipeline_build_count_total; /**< Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_counter_t systems_ran_frame; /**< Number of systems ran in the last frame. */ + + /** Current position in ringbuffer */ + int32_t t; +} ecs_world_stats_t; + +/* Statistics for a single query (use ecs_get_query_stats) */ +typedef struct ecs_query_stats_t { + ecs_gauge_t matched_table_count; /**< Number of matched non-empty tables. This is the number of tables + * iterated over when evaluating a query. */ + + ecs_gauge_t matched_empty_table_count; /**< Number of matched empty tables. Empty tables are not iterated over when + * evaluating a query. */ + + ecs_gauge_t matched_entity_count; /**< Number of matched entities across all tables */ + + /** Current position in ringbuffer */ + int32_t t; +} ecs_query_stats_t; + +/** Statistics for a single system (use ecs_get_system_stats) */ +typedef struct ecs_system_stats_t { + ecs_query_stats_t query_stats; + ecs_counter_t time_spent; /**< Time spent processing a system */ + ecs_counter_t invoke_count; /**< Number of times system is invoked */ + ecs_gauge_t active; /**< Whether system is active (is matched with >0 entities) */ + ecs_gauge_t enabled; /**< Whether system is enabled */ +} ecs_system_stats_t; + +/** Statistics for all systems in a pipeline. */ +typedef struct ecs_pipeline_stats_t { + /** Vector with system ids of all systems in the pipeline. The systems are + * stored in the order they are executed. Merges are represented by a 0. */ + ecs_vector_t *systems; + + /** Map with system statistics. For each system in the systems vector, an + * entry in the map exists of type ecs_system_stats_t. */ + ecs_map_t *system_stats; +} ecs_pipeline_stats_t; + +/** Get world statistics. + * Obtain statistics for the provided world. This operation loops several times + * over the tables in the world, and can impact application performance. + * + * @param world The world. + * @param stats Out parameter for statistics. + */ +FLECS_API void ecs_get_world_stats( + const ecs_world_t *world, + ecs_world_stats_t *stats); + +/** Print world statistics. + * Print statistics obtained by ecs_get_world_statistics and in the + * ecs_world_info_t struct. + * + * @param world The world. + * @param stats The statistics to print. + */ +FLECS_API void ecs_dump_world_stats( + 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_get_query_stats( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s); + +#ifdef FLECS_SYSTEM +/** 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_get_system_stats( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *stats); +#endif + +#ifdef FLECS_PIPELINE +/** Get pipeline statistics. + * Obtain statistics for the provided pipeline. + * + * @param world The world. + * @param pipeline The pipeline. + * @param stats Out parameter for statistics. + * @return true if success, false if not a pipeline. + */ +FLECS_API bool ecs_get_pipeline_stats( + const ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *stats); +#endif + +FLECS_API void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif +#endif + +#ifdef __cplusplus +} + +#ifndef FLECS_NO_CPP +#ifndef FLECS_LEGACY +/** + * @file flecs.hpp + * @brief Flecs C++ API. + * + * This is a C++11 wrapper around the Flecs C API. + */ + +#pragma once + +// The C++ API does not use STL, save for type_traits +#include <type_traits> + +// 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 + +namespace flecs { + +//////////////////////////////////////////////////////////////////////////////// +//// Forward declarations and types +//////////////////////////////////////////////////////////////////////////////// + +using world_t = ecs_world_t; +using id_t = ecs_id_t; +using entity_t = ecs_entity_t; +using type_t = ecs_type_t; +using snapshot_t = ecs_snapshot_t; +using filter_t = ecs_filter_t; +using query_t = ecs_query_t; +using ref_t = ecs_ref_t; +using iter_t = ecs_iter_t; +using ComponentLifecycle = EcsComponentLifecycle; + +enum inout_kind_t { + InOutDefault = EcsInOutDefault, + 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 var_kind_t { + VarDefault = EcsVarDefault, + VarIsEntity = EcsVarIsEntity, + VarIsVariable = EcsVarIsVariable +}; + +class world; +class world_async_stage; +class snapshot; +class id; +class entity; +class entity_view; +class type; +class pipeline; +class iter; +class term; +class filter_iterator; +class child_iterator; +class world_filter; +class snapshot_filter; +class query_base; + +template<typename ... Components> +class filter; + +template<typename ... Components> +class query; + +template<typename ... Components> +class system; + +template<typename ... Components> +class observer; + +template <typename ... Components> +class filter_builder; + +template <typename ... Components> +class query_builder; + +template <typename ... Components> +class system_builder; + +template <typename ... Components> +class observer_builder; + +namespace _ +{ +template <typename T, typename U = int> +class cpp_type; + +template <typename Func, typename ... Components> +class each_invoker; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Builtin components and tags +//////////////////////////////////////////////////////////////////////////////// + +/* Builtin components */ +using Component = EcsComponent; +using Type = EcsType; +using Identifier = EcsIdentifier; +using Timer = EcsTimer; +using RateFilter = EcsRateFilter; +using TickSource = EcsTickSource; +using Query = EcsQuery; +using Trigger = EcsTrigger; +using Observer = EcsObserver; + +/* Builtin opaque components */ +static const flecs::entity_t System = ecs_id(EcsSystem); + +/* Builtin set constants */ +static const uint8_t DefaultSet = EcsDefaultSet; +static const uint8_t Self = EcsSelf; +static const uint8_t SuperSet = EcsSuperSet; +static const uint8_t SubSet = EcsSubSet; +static const uint8_t Cascade = EcsCascade; +static const uint8_t All = EcsAll; +static const uint8_t Nothing = EcsNothing; + +/* Builtin tag ids */ +static const flecs::entity_t Module = EcsModule; +static const flecs::entity_t Prefab = EcsPrefab; +static const flecs::entity_t Hidden = EcsHidden; +static const flecs::entity_t Disabled = EcsDisabled; +static const flecs::entity_t DisabledIntern = EcsDisabledIntern; +static const flecs::entity_t Inactive = EcsInactive; +static const flecs::entity_t OnDemand = EcsOnDemand; +static const flecs::entity_t Monitor = EcsMonitor; +static const flecs::entity_t Pipeline = EcsPipeline; + +/* Trigger tags */ +static const flecs::entity_t OnAdd = EcsOnAdd; +static const flecs::entity_t OnRemove = EcsOnRemove; +static const flecs::entity_t OnSet = EcsOnSet; +static const flecs::entity_t UnSet = EcsUnSet; + +/* Builtin pipeline tags */ +static const flecs::entity_t PreFrame = EcsPreFrame; +static const flecs::entity_t OnLoad = EcsOnLoad; +static const flecs::entity_t PostLoad = EcsPostLoad; +static const flecs::entity_t PreUpdate = EcsPreUpdate; +static const flecs::entity_t OnUpdate = EcsOnUpdate; +static const flecs::entity_t OnValidate = EcsOnValidate; +static const flecs::entity_t PostUpdate = EcsPostUpdate; +static const flecs::entity_t PreStore = EcsPreStore; +static const flecs::entity_t OnStore = EcsOnStore; +static const flecs::entity_t PostFrame = EcsPostFrame; + +/** Builtin roles */ +static const flecs::entity_t Pair = ECS_PAIR; +static const flecs::entity_t Switch = ECS_SWITCH; +static const flecs::entity_t Case = ECS_CASE; +static const flecs::entity_t Owned = ECS_OWNED; + +/* Builtin entity ids */ +static const flecs::entity_t Flecs = EcsFlecs; +static const flecs::entity_t FlecsCore = EcsFlecsCore; +static const flecs::entity_t World = EcsWorld; + +/* Ids used by rule solver */ +static const flecs::entity_t Wildcard = EcsWildcard; +static const flecs::entity_t This = EcsThis; +static const flecs::entity_t Transitive = EcsTransitive; +static const flecs::entity_t Final = EcsFinal; +static const flecs::entity_t Tag = EcsTag; + +/* Builtin relationships */ +static const flecs::entity_t IsA = EcsIsA; +static const flecs::entity_t ChildOf = EcsChildOf; + +/* Builtin identifiers */ +static const flecs::entity_t Name = EcsName; +static const flecs::entity_t Symbol = EcsSymbol; + +/* Cleanup rules */ +static const flecs::entity_t OnDelete = EcsOnDelete; +static const flecs::entity_t OnDeleteObject = EcsOnDeleteObject; +static const flecs::entity_t Remove = EcsRemove; +static const flecs::entity_t Delete = EcsDelete; +static const flecs::entity_t Throw = EcsThrow; + +} + +//////////////////////////////////////////////////////////////////////////////// +//// 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) + +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<class Ty> inline void destruct_obj(Ty* _ptr) { _ptr->~Ty(); } +template<class Ty> inline void free_obj(Ty* _ptr) { + if (_ptr) { + destruct_obj(_ptr); + ecs_os_free(_ptr); + } +} + +} // namespace _ + +} // namespace flecs + +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 <bool V, typename T, typename F> +using conditional_t = typename std::conditional<V, T, F>::type; + +template <typename T> +using decay_t = typename std::decay<T>::type; + +template <bool V, typename T = void> +using enable_if_t = typename std::enable_if<V, T>::type; + +template <typename T> +using remove_pointer_t = typename std::remove_pointer<T>::type; + +template <typename T> +using remove_reference_t = typename std::remove_reference<T>::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; + + +// 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<class Src, class Dst> +using transcribe_const_t = conditional_t<is_const<Src>::value, Dst const, Dst>; + +template<class Src, class Dst> +using transcribe_volatile_t = conditional_t<is_volatile<Src>::value, Dst volatile, Dst>; + +template<class Src, class Dst> +using transcribe_cv_t = transcribe_const_t< Src, transcribe_volatile_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<true>* = nullptr +// vs: +// if_t<true> = 0 + +template <bool V> +using if_t = enable_if_t<V, int>; + +template <bool V> +using if_not_t = enable_if_t<false == V, int>; + + +// String handling + +class 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. +class string { +public: + explicit string() + : m_str(nullptr) + , m_const_str("") + , m_length(0) { } + + explicit string(char *str) + : m_str(str) + , m_const_str(str ? str : "") + , m_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 (m_str) { + ecs_os_free(m_str); + } + } + + string(string&& str) { + ecs_os_free(m_str); + m_str = str.m_str; + m_const_str = str.m_const_str; + m_length = str.m_length; + str.m_str = nullptr; + } + + operator const char*() const { + return m_const_str; + } + + string& operator=(string&& str) { + ecs_os_free(m_str); + m_str = str.m_str; + m_const_str = str.m_const_str; + m_length = str.m_length; + str.m_str = nullptr; + return *this; + } + + // Ban implicit copies/allocations + string& operator=(const string& str) = delete; + string(const string& str) = delete; + + bool operator==(const flecs::string& str) const { + if (str.m_const_str == m_const_str) { + return true; + } + + if (!m_const_str || !str.m_const_str) { + return false; + } + + if (str.m_length != m_length) { + return false; + } + + return ecs_os_strcmp(str, m_const_str) == 0; + } + + bool operator!=(const flecs::string& str) const { + return !(*this == str); + } + + bool operator==(const char *str) const { + if (m_const_str == str) { + return true; + } + + if (!m_const_str || !str) { + return false; + } + + return ecs_os_strcmp(str, m_const_str) == 0; + } + + bool operator!=(const char *str) const { + return !(*this == str); + } + + const char* c_str() const { + return m_const_str; + } + + std::size_t length() { + return static_cast<std::size_t>(m_length); + } + + std::size_t size() { + return length(); + } + + void clear() { + ecs_os_free(m_str); + m_str = nullptr; + m_const_str = nullptr; + } + +protected: + // Must be constructed through string_view. This allows for using the string + // class for both owned and non-owned strings, which can reduce allocations + // when code conditionally should store a literal or an owned string. + // Making this constructor private forces the code to explicitly create a + // string_view which emphasizes that the string won't be freed by the class. + string(const char *str) + : m_str(nullptr) + , m_const_str(str ? str : "") + , m_length(str ? ecs_os_strlen(str) : 0) { } + + char *m_str = nullptr; + const char *m_const_str; + ecs_size_t m_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. +class string_view : public string { +public: + explicit string_view(const char *str) + : string(str) { } +}; + +// Wrapper around ecs_strbuf_t that provides a simple stringstream like API. +class stringstream { +public: + explicit stringstream() + : m_buf({}) { } + + ~stringstream() { + ecs_strbuf_reset(&m_buf); + } + + stringstream(stringstream&& str) { + ecs_strbuf_reset(&m_buf); + m_buf = str.m_buf; + str.m_buf = {}; + } + + stringstream& operator=(stringstream&& str) { + ecs_strbuf_reset(&m_buf); + m_buf = str.m_buf; + str.m_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(&m_buf, str); + return *this; + } + + flecs::string str() { + return flecs::string(ecs_strbuf_get(&m_buf)); + } + +private: + ecs_strbuf_t m_buf; +}; + +// Array class. Simple std::array like utility that is mostly there to aid +// template code, where the expanded array size would be 0. +template <typename T> +class array_iterator +{ +public: + explicit array_iterator(T* value, int index) { + m_value = value; + m_index = index; + } + + bool operator!=(array_iterator const& other) const + { + return m_index != other.m_index; + } + + T & operator*() const + { + return m_value[m_index]; + } + + array_iterator& operator++() + { + ++m_index; + return *this; + } + +private: + T* m_value; + int m_index; +}; + +template <typename T, size_t Size, class Enable = void> +class array { }; + +template <typename T, size_t Size> +class array<T, Size, enable_if_t<Size != 0> > { +public: + array() {}; + + array(const T (&elems)[Size]) { + int i = 0; + for (auto it = this->begin(); it != this->end(); ++ it) { + *it = elems[i ++]; + } + } + + T& operator[](size_t index) { + return m_array[index]; + } + + array_iterator<T> begin() { + return array_iterator<T>(m_array, 0); + } + + array_iterator<T> end() { + return array_iterator<T>(m_array, Size); + } + + size_t size() { + return Size; + } + + T* ptr() { + return m_array; + } +private: + T m_array[Size]; +}; + +// Specialized class for zero-sized array +template <typename T, size_t Size> +class array<T, Size, enable_if_t<Size == 0>> { +public: + array() {}; + array(const T* (&elems)) { (void)elems; } + T operator[](size_t index) { abort(); (void)index; return T(); } + array_iterator<T> begin() { return array_iterator<T>(nullptr, 0); } + array_iterator<T> end() { return array_iterator<T>(nullptr, 0); } + + size_t size() { + return 0; + } + + T* ptr() { + return NULL; + } +}; + +namespace _ +{ + +// Utility to prevent static assert from immediately triggering +template <class... T> +struct always_false { + static const bool value = false; +}; + +} // namespace _ + +} // namespace flecs + +namespace flecs { + +namespace _ { + struct pair_base { }; +} // _ + + +// Type that represents a pair and can encapsulate a temporary value +template <typename R, typename O> +struct pair : _::pair_base { + // Traits used to deconstruct the pair + + // The actual type of the pair is determined by which type of the pair is + // empty. If both types are empty or not empty, the pair assumes the type + // of the relation. + using type = conditional_t<!is_empty<R>::value || is_empty<O>::value, R, O>; + using relation = R; + using object = O; + + pair(type& v) : ref_(v) { } + + // This allows the class to be used as a temporary object + pair(const type& v) : ref_(const_cast<type&>(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 <typename R, typename O, if_t<is_empty<R>::value> = 0> +using pair_object = pair<R, O>; + + +// Utilities to test if type is a pair +template <typename T> +struct is_pair { + static constexpr bool value = is_base_of<_::pair_base, remove_reference_t<T> >::value; +}; + + +// Get actual type, relation or object from pair while preserving cv qualifiers. +template <typename P> +using pair_relation_t = transcribe_cv_t<remove_reference_t<P>, typename remove_reference_t<P>::relation>; + +template <typename P> +using pair_object_t = transcribe_cv_t<remove_reference_t<P>, typename remove_reference_t<P>::object>; + +template <typename P> +using pair_type_t = transcribe_cv_t<remove_reference_t<P>, typename remove_reference_t<P>::type>; + + +// Get actual type from a regular type or pair +template <typename T, typename U = int> +struct actual_type; + +template <typename T> +struct actual_type<T, if_not_t< is_pair<T>::value >> { + using type = T; +}; + +template <typename T> +struct actual_type<T, if_t< is_pair<T>::value >> { + using type = pair_type_t<T>; +}; + +template <typename T> +using actual_type_t = typename actual_type<T>::type; + + +// Get type without const, *, & +template<typename T> +struct base_type { + using type = remove_pointer_t< decay_t< actual_type_t<T> > >; +}; + +template <typename T> +using base_type_t = typename base_type<T>::type; + + +// Get type without *, & (retains const which is useful for function args) +template<typename T> +struct base_arg_type { + using type = remove_pointer_t< remove_reference_t< actual_type_t<T> > >; +}; + +template <typename T> +using base_arg_type_t = typename base_arg_type<T>::type; + + +// Test if type is the same as its actual type +template <typename T> +struct is_actual { + static constexpr bool value = std::is_same<T, actual_type_t<T> >::value; +}; + +} // flecs + +// Neat utility to inspect arguments & returntype of a function type +// Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work + +namespace flecs { +namespace _ { + +template <typename ... Args> +struct arg_list { }; + +// Base type that contains the traits +template <typename ReturnType, typename... Args> +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<Args ...>; +}; + +// Primary template for function_traits_impl +template <typename T> +struct function_traits_impl { + static constexpr bool is_callable = false; +}; + +// Template specializations for the different kinds of function types (whew) +template <typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(Args...)> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(*)(Args...)> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...)> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&&> + : function_traits_defs<ReturnType, Args...> {}; + +// 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 <typename T, typename V = void> +struct function_traits_no_cv + : function_traits_impl<T> {}; + +// Specialized template for function types +template <typename T> +struct function_traits_no_cv<T, decltype((void)&T::operator())> + : function_traits_impl<decltype(&T::operator())> {}; + +// Front facing template that decays T before ripping it apart. +template <typename T> +struct function_traits + : function_traits_no_cv< decay_t<T> > {}; + +} // _ + + +template <typename T> +struct is_callable { + static constexpr bool value = _::function_traits<T>::is_callable; +}; + +template <typename T> +struct arity { + static constexpr int value = _::function_traits<T>::arity; +}; + +template <typename T> +using return_type_t = typename _::function_traits<T>::return_type; + +template <typename T> +using arg_list_t = typename _::function_traits<T>::args; + + +template<typename Func, typename ... Args> +struct first_arg_impl; + +template<typename Func, typename T, typename ... Args> +struct first_arg_impl<Func, _::arg_list<T, Args ...> > { + using type = T; +}; + +template<typename Func> +struct first_arg { + using type = typename first_arg_impl<Func, arg_list_t<Func>>::type; +}; + +template <typename Func> +using first_arg_t = typename first_arg<Func>::type; + +} // flecs +namespace flecs +{ + +namespace _ +{ + +inline void ecs_ctor_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot default construct %s, add %s::%s() or use emplace<T>", + path, path, ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_dtor_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, "cannnot destruct %s, add ~%s::%s()", + path, path, ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_copy_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + const ecs_entity_t*, void *, const void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot copy assign %s, add %s& %s::operator =(const %s&)", path, + ecs_get_name(w, id), path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_move_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + const ecs_entity_t*, void *, void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot move assign %s, add %s& %s::operator =(%s&&)", path, + ecs_get_name(w, id), path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_copy_ctor_illegal(ecs_world_t* w, ecs_entity_t id, + const EcsComponentLifecycle*, const ecs_entity_t*, const ecs_entity_t*, + void *, const void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot copy construct %s, add %s::%s(const %s&)", + path, path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_move_ctor_illegal(ecs_world_t* w, ecs_entity_t id, + const EcsComponentLifecycle*, const ecs_entity_t*, const ecs_entity_t*, + void *, void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot move construct %s, add %s::%s(%s&&)", + path, path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + + +// T() +// Can't coexist with T(flecs::entity) or T(flecs::world, flecs::entity) +template <typename T> +void ctor_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, void *ptr, size_t size, + int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast<T*>(ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&arr[i], T); + } +} + +// T(flecs::world, flecs::entity) +template <typename T> +void ctor_world_entity_impl( + ecs_world_t* world, ecs_entity_t, const ecs_entity_t* ids, void *ptr, + size_t size, int32_t count, void*); + +// ~T() +template <typename T> +void dtor_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, void *ptr, size_t size, + int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast<T*>(ptr); + for (int i = 0; i < count; i ++) { + arr[i].~T(); + } +} + +// T& operator=(const T&) +template <typename T> +void copy_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, const ecs_entity_t*, + void *dst_ptr, const void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + const T *src_arr = static_cast<const T*>(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = src_arr[i]; + } +} + +// T& operator=(T&&) +template <typename T> +void move_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, const ecs_entity_t*, + void *dst_ptr, void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = std::move(src_arr[i]); + } +} + +// T(T&) +template <typename T> +void copy_ctor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + const void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + const T *src_arr = static_cast<const T*>(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(src_arr[i])); + } +} + +// T(T&&) +template <typename T> +void move_ctor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(std::move(src_arr[i]))); + } +} + +// T(T&&), ~T() +// Typically used when moving to a new table, and removing from the old table +template <typename T> +void ctor_move_dtor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(std::move(src_arr[i]))); + src_arr[i].~T(); + } +} + +// Move assign + dtor (non-trivial move assigmnment) +// Typically used when moving a component to a deleted component +template <typename T, if_not_t< + std::is_trivially_move_assignable<T>::value > = 0> +void move_dtor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + // Move assignment should free dst & assign dst to src + dst_arr[i] = std::move(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 assigmnment) +// Typically used when moving a component to a deleted component +template <typename T, if_t< + std::is_trivially_move_assignable<T>::value > = 0> +void move_dtor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + // Cleanup resources of dst + dst_arr[i].~T(); + // Copy src to dst + dst_arr[i] = std::move(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 has flecs constructor +template <typename T> +struct has_flecs_ctor { + static constexpr bool value = + std::is_constructible<actual_type_t<T>, + flecs::world&, flecs::entity>::value; +}; + +// Trait to test if type is constructible by flecs +template <typename T> +struct is_flecs_constructible { + static constexpr bool value = + std::is_default_constructible<actual_type_t<T>>::value || + std::is_constructible<actual_type_t<T>, + flecs::world&, flecs::entity>::value; +}; + +// Trait to test if type has a self constructor (flecs::entity, Args...) +template <typename T, typename ... Args> +struct is_self_constructible { + static constexpr bool value = + std::is_constructible<actual_type_t<T>, + flecs::entity, Args...>::value; +}; + +namespace _ +{ + +// Trivially constructible +template <typename T, if_t< std::is_trivially_constructible<T>::value > = 0> +ecs_xtor_t ctor() { + return nullptr; +} + +// Not constructible by flecs +template <typename T, if_t< + ! std::is_default_constructible<T>::value && + ! has_flecs_ctor<T>::value > = 0> +ecs_xtor_t ctor() { + return ecs_ctor_illegal; +} + +// Default constructible +template <typename T, if_t< + ! std::is_trivially_constructible<T>::value && + std::is_default_constructible<T>::value && + ! has_flecs_ctor<T>::value > = 0> +ecs_xtor_t ctor() { + return ctor_impl<T>; +} + +// Flecs constructible: T(flecs::world, flecs::entity) +template <typename T, if_t< has_flecs_ctor<T>::value > = 0> +ecs_xtor_t ctor() { + return ctor_world_entity_impl<T>; +} + +// No dtor +template <typename T, if_t< std::is_trivially_destructible<T>::value > = 0> +ecs_xtor_t dtor() { + return nullptr; +} + +// Dtor +template <typename T, if_t< + std::is_destructible<T>::value && + ! std::is_trivially_destructible<T>::value > = 0> +ecs_xtor_t dtor() { + return dtor_impl<T>; +} + +// Assert when the type cannot be destructed +template <typename T, if_not_t< std::is_destructible<T>::value > = 0> +ecs_xtor_t dtor() { + flecs_static_assert(always_false<T>::value, + "component type must be destructible"); + return ecs_dtor_illegal; +} + +// Trivially copyable +template <typename T, if_t< std::is_trivially_copyable<T>::value > = 0> +ecs_copy_t copy() { + return nullptr; +} + +// Not copyable +template <typename T, if_t< + ! std::is_trivially_copyable<T>::value && + ! std::is_copy_assignable<T>::value > = 0> +ecs_copy_t copy() { + return ecs_copy_illegal; +} + +// Copy assignment +template <typename T, if_t< + std::is_copy_assignable<T>::value && + ! std::is_trivially_copyable<T>::value > = 0> +ecs_copy_t copy() { + return copy_impl<T>; +} + +// Trivially move assignable +template <typename T, if_t< std::is_trivially_move_assignable<T>::value > = 0> +ecs_move_t move() { + return nullptr; +} + +// Component types must be move assignable +template <typename T, if_not_t< std::is_move_assignable<T>::value > = 0> +ecs_move_t move() { + flecs_static_assert(always_false<T>::value, + "component type must be move assignable"); + return ecs_move_illegal; +} + +// Move assignment +template <typename T, if_t< + std::is_move_assignable<T>::value && + ! std::is_trivially_move_assignable<T>::value > = 0> +ecs_move_t move() { + return move_impl<T>; +} + +// Trivially copy constructible +template <typename T, if_t< + std::is_trivially_copy_constructible<T>::value > = 0> +ecs_copy_ctor_t copy_ctor() { + return nullptr; +} + +// No copy ctor +template <typename T, if_t< ! std::is_copy_constructible<T>::value > = 0> +ecs_copy_ctor_t copy_ctor() { + return ecs_copy_ctor_illegal; +} + +// Copy ctor +template <typename T, if_t< + std::is_copy_constructible<T>::value && + ! std::is_trivially_copy_constructible<T>::value > = 0> +ecs_copy_ctor_t copy_ctor() { + return copy_ctor_impl<T>; +} + +// Trivially move constructible +template <typename T, if_t< + std::is_trivially_move_constructible<T>::value > = 0> +ecs_move_ctor_t move_ctor() { + return nullptr; +} + +// Component types must be move constructible +template <typename T, if_not_t< std::is_move_constructible<T>::value > = 0> +ecs_move_ctor_t move_ctor() { + flecs_static_assert(always_false<T>::value, + "component type must be move constructible"); + return ecs_move_ctor_illegal; +} + +// Move ctor +template <typename T, if_t< + std::is_move_constructible<T>::value && + ! std::is_trivially_move_constructible<T>::value > = 0> +ecs_move_ctor_t move_ctor() { + return move_ctor_impl<T>; +} + +// Trivial merge (move assign + dtor) +template <typename T, if_t< + std::is_trivially_move_constructible<T>::value && + std::is_trivially_destructible<T>::value > = 0> +ecs_move_ctor_t ctor_move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template <typename T, if_t< + ! std::is_move_constructible<T>::value || + ! std::is_destructible<T>::value > = 0> +ecs_move_ctor_t ctor_move_dtor() { + flecs_static_assert(always_false<T>::value, + "component type must be move constructible and destructible"); + return ecs_move_ctor_illegal; +} + +// Merge ctor + dtor +template <typename T, if_t< + !(std::is_trivially_move_constructible<T>::value && + std::is_trivially_destructible<T>::value) && + std::is_move_constructible<T>::value && + std::is_destructible<T>::value > = 0> +ecs_move_ctor_t ctor_move_dtor() { + return ctor_move_dtor_impl<T>; +} + +// Trivial merge (move assign + dtor) +template <typename T, if_t< + std::is_trivially_move_assignable<T>::value && + std::is_trivially_destructible<T>::value > = 0> +ecs_move_ctor_t move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template <typename T, if_t< + ! std::is_move_assignable<T>::value || + ! std::is_destructible<T>::value > = 0> +ecs_move_ctor_t move_dtor() { + flecs_static_assert(always_false<T>::value, + "component type must be move constructible and destructible"); + return ecs_move_ctor_illegal; +} + +// Merge assign + dtor +template <typename T, if_t< + !(std::is_trivially_move_assignable<T>::value && + std::is_trivially_destructible<T>::value) && + std::is_move_assignable<T>::value && + std::is_destructible<T>::value > = 0> +ecs_move_ctor_t move_dtor() { + return move_dtor_impl<T>; +} + +} // _ +} // flecs + +namespace flecs +{ + +/** Unsafe wrapper class around a column. + * This class can be used when a system does not know the type of a column at + * compile time. + */ +class unsafe_column { +public: + unsafe_column(void* array, size_t size, size_t count, bool is_shared = false) + : m_array(array) + , m_size(size) + , m_count(count) + , m_is_shared(is_shared) {} + + /** Return element in component array. + * This operator may only be used if the column is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + void* operator[](size_t index) { + ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + ecs_assert(!m_is_shared, ECS_INVALID_PARAMETER, NULL); + return ECS_OFFSET(m_array, m_size * index); + } + + /** Return whether component is set. + * If the column is optional, this method may return false. + * + * @return True if component is set, false if component is not set. + */ + bool is_set() const { + return m_array != nullptr; + } + + /** Return whether component is shared. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_shared() const { + return m_is_shared; + } + +protected: + void* m_array; + size_t m_size; + size_t m_count; + bool m_is_shared; +}; + +/** Wrapper class around a column. + * + * @tparam T component type of the column. + */ +template <typename T> +class column { +public: + static_assert(std::is_empty<T>::value == false, + "invalid type for column, cannot iterate empty type"); + + /** Create column from component array. + * + * @param array Pointer to the component array. + * @param count Number of elements in component array. + * @param is_shared Is the component shared or not. + */ + column(T* array, size_t count, bool is_shared = false) + : m_array(array) + , m_count(count) + , m_is_shared(is_shared) {} + + /** Create column from iterator. + * + * @param iter Iterator object. + * @param column Index of the signature of the query being iterated over. + */ + column(iter &iter, int column); + + /** Return element in component array. + * This operator may only be used if the column is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + T& operator[](size_t index) { + ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + ecs_assert(!index || !m_is_shared, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array[index]; + } + + /** Return first element of component array. + * This operator is typically used when the column is shared. + * + * @return Reference to the first element. + */ + T& operator*() { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return *m_array; + } + + /** Return first element of component array. + * This operator is typically used when the column is shared. + * + * @return Pointer to the first element. + */ + T* operator->() { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array; + } + + /** Return whether component is set. + * If the column is optional, this method may return false. + * + * @return True if component is set, false if component is not set. + */ + bool is_set() const { + return m_array != nullptr; + } + + /** Return whether component is shared. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_shared() const { + return m_is_shared; + } + + /** Return whether component is owned. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_owned() const { + return !m_is_shared; + } + +protected: + T* m_array; + size_t m_count; + bool m_is_shared; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +namespace _ { + +//////////////////////////////////////////////////////////////////////////////// + +/** Iterate over an integer range (used to iterate over entity range). + * + * @tparam Type of the iterator + */ +template <typename T> +class range_iterator +{ +public: + explicit range_iterator(T value) + : m_value(value){} + + bool operator!=(range_iterator const& other) const + { + return m_value != other.m_value; + } + + T const& operator*() const + { + return m_value; + } + + range_iterator& operator++() + { + ++m_value; + return *this; + } + +private: + T m_value; +}; + +} // namespace _ + +} // namespace flecs + +#ifdef FLECS_DEPRECATED + +namespace flecs +{ + +/* Deprecated functions */ +template<typename Base> +class iter_deprecated { +public: + ECS_DEPRECATED("use term_count(int32_t)") + int32_t column_count() const { + return base()->term_count(); + } + + ECS_DEPRECATED("use term_size(int32_t)") + size_t column_size(int32_t col) const { + return base()->term_size(col); + } + + ECS_DEPRECATED("use is_owned(int32_t)") + bool is_shared(int32_t col) const { + return !base()->is_owned(col); + } + + ECS_DEPRECATED("use term_source(int32_t)") + flecs::entity column_source(int32_t col) const; + + ECS_DEPRECATED("use term_id(int32_t)") + flecs::entity column_entity(int32_t col) const; + + ECS_DEPRECATED("no replacement") + flecs::type column_type(int32_t col) const; + + ECS_DEPRECATED("use type()") + type table_type() const; + + template <typename T, if_t< is_const<T>::value > = 0> + ECS_DEPRECATED("use term<const T>(int32_t)") + flecs::column<T> column(int32_t col) const { + return base()->template term<T>(col); + } + + template <typename T, if_not_t< is_const<T>::value > = 0> + ECS_DEPRECATED("use term<T>(int32_t)") + flecs::column<T> column(int32_t col) const { + ecs_assert(!ecs_is_readonly(iter(), col), + ECS_COLUMN_ACCESS_VIOLATION, NULL); + return base()->template term<T>(col); + } + + ECS_DEPRECATED("use term(int32_t)") + flecs::unsafe_column column(int32_t col) const { + return base()->term(col); + } + + template <typename T> + ECS_DEPRECATED("use owned<T>(int32_t)") + flecs::column<T> owned(int32_t col) const { + return base()->template owned<T>(col); + } + + template <typename T> + ECS_DEPRECATED("use shared<T>(int32_t)") + const T& shared(int32_t col) const { + return base()->template shared<T>(col); + } + + template <typename T, if_t< is_const<T>::value > = 0> + ECS_DEPRECATED("no replacement") + T& element(int32_t col, int32_t row) const { + return base()->template get_element<T>(col, row); + } + + template <typename T, if_not_t< is_const<T>::value > = 0> + ECS_DEPRECATED("no replacement") + T& element(int32_t col, int32_t row) const { + ecs_assert(!ecs_is_readonly(iter(), col), + ECS_COLUMN_ACCESS_VIOLATION, NULL); + return base()->template get_element<T>(col, row); + } + +private: + const Base* base() const { return static_cast<const Base*>(this); } + const flecs::iter_t* iter() const { return base()->c_ptr(); } +}; + +} +#else +namespace flecs +{ +template <typename Base> +class iter_deprecated { }; +} +#endif + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// + +/** Class that enables iterating over table columns. + */ +class iter : public iter_deprecated<iter> { + using row_iterator = _::range_iterator<size_t>; +public: + /** Construct iterator from C iterator object. + * This operation is typically not invoked directly by the user. + * + * @param it Pointer to C iterator. + */ + iter(const ecs_iter_t *it) : m_iter(it) { + m_begin = 0; + m_end = static_cast<std::size_t>(it->count); + } + + row_iterator begin() const { + return row_iterator(m_begin); + } + + row_iterator end() const { + return row_iterator(m_end); + } + + /** Obtain handle to current system. + */ + flecs::entity system() const; + + /** Obtain current world. + */ + flecs::world world() const; + + /** Obtain pointer to C iterator object + */ + const flecs::iter_t* c_ptr() const { + return m_iter; + } + + /** Number of entities to iterate over. + */ + size_t count() const { + return static_cast<size_t>(m_iter->count); + } + + /** Return delta_time of current frame. + */ + FLECS_FLOAT delta_time() const { + return m_iter->delta_time; + } + + /** Return time elapsed since last time system was invoked. + */ + FLECS_FLOAT delta_system_time() const { + return m_iter->delta_system_time; + } + + /** Return total time passed in simulation. + */ + FLECS_FLOAT world_time() const { + return m_iter->world_time; + } + + /** Obtain type of the entities being iterated over. + */ + flecs::type type() const; + + /** Is current type a module or does it contain module contents? */ + bool has_module() const { + return ecs_table_has_module(ecs_iter_table(m_iter)); + } + + /** Access self. + * 'self' is an entity that can be associated with a trigger, observer or + * system when they are created. */ + flecs::entity self() const; + + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + void* ctx() { + return m_iter->ctx; + } + + /** Access param. + * param contains the pointer passed to the param argument of system::run + */ + void* param() { + return m_iter->param; + } + + /** Obtain mutable handle to entity being iterated over. + * + * @param row Row being iterated over. + */ + flecs::entity entity(size_t row) const; + + /** Obtain the total number of inactive tables the query is matched with. + */ + int32_t inactive_table_count() const { + return m_iter->inactive_table_count; + } + + /** Returns whether term is owned. + * + * @param index The term index. + */ + bool is_owned(int32_t index) const { + return ecs_term_is_owned(m_iter, index); + } + + /** Returns whether term is set. + * + * @param index The term index. + */ + bool is_set(int32_t index) const { + return ecs_term_is_set(m_iter, index); + } + + /** Returns whether term is readonly. + * + * @param index The term index. + */ + bool is_readonly(int32_t index) const { + return ecs_term_is_readonly(m_iter, index); + } + + /** Number of terms in iteator. + */ + int32_t term_count() const { + return m_iter->column_count; + } + + /** Size of term data type. + * + * @param index The term id. + */ + size_t term_size(int32_t index) const { + return ecs_term_size(m_iter, index); + } + + /** Obtain term source (0 if self) + * + * @param index The term index. + */ + flecs::entity term_source(int32_t index) const; + + /** Obtain component/tag entity of term. + * + * @param index The term index. + */ + flecs::entity term_id(int32_t index) const; + + /** Obtain term with const type. + * If the specified term index does not match with the provided type, the + * function will assert. + * + * @tparam T Type of the term. + * @param index The term index. + * @return The term data. + */ + template <typename T, typename A = actual_type_t<T>, + typename std::enable_if<std::is_const<T>::value, void>::type* = nullptr> + + flecs::column<A> term(int32_t index) const { + return get_term<A>(index); + } + + /** Obtain term with non-const type. + * If the specified term id does not match with the provided type or if + * the term is readonly, the function will assert. + * + * @tparam T Type of the term. + * @param index The term index. + * @return The term data. + */ + template <typename T, typename A = actual_type_t<T>, + typename std::enable_if< + std::is_const<T>::value == false, void>::type* = nullptr> + + flecs::column<A> term(int32_t index) const { + ecs_assert(!ecs_term_is_readonly(m_iter, index), + ECS_COLUMN_ACCESS_VIOLATION, NULL); + return get_term<A>(index); + } + + /** Obtain unsafe term. + * Unsafe terms are required when a system does not know at compile time + * which component will be passed to it. + * + * @param index The term index. + */ + flecs::unsafe_column term(int32_t index) const { + return get_unsafe_term(index); + } + + /** Obtain owned term. + * Same as iter::term, but ensures that term is owned. + * + * @tparam Type of the term. + * @param index The term index. + * @return The term data. + */ + template <typename T, typename A = actual_type_t<T>> + flecs::column<A> term_owned(int32_t index) const { + ecs_assert(!!ecs_is_owned(m_iter, index), ECS_COLUMN_IS_SHARED, NULL); + return this->term<A>(index); + } + + /** Obtain shared term. + * Same as iter::term, but ensures that term is shared. + * + * @tparam Type of the term. + * @param index The term index. + * @return The component term. + */ + template <typename T, typename A = actual_type_t<T>> + const T& term_shared(int32_t index) const { + ecs_assert( + ecs_term_id(m_iter, index) == + _::cpp_type<T>::id(m_iter->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); + + ecs_assert(!ecs_term_is_owned(m_iter, index), + ECS_COLUMN_IS_NOT_SHARED, NULL); + + return *static_cast<A*>(ecs_term_w_size(m_iter, sizeof(A), index)); + } + + /** Obtain the total number of tables the iterator will iterate over. + */ + int32_t table_count() const { + return m_iter->table_count; + } + + /** Obtain untyped pointer to table column. + * + * @param table_column Id of table column (corresponds with location in table type). + * @return Pointer to table column. + */ + void* table_column(int32_t col) const { + return ecs_iter_column_w_size(m_iter, 0, col); + } + + /** Obtain typed pointer to table column. + * If the table does not contain a column with the specified type, the + * function will assert. + * + * @tparam T Type of the table column. + */ + template <typename T, typename A = actual_type_t<T>> + flecs::column<T> table_column() const { + auto col = ecs_iter_find_column(m_iter, _::cpp_type<T>::id()); + ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); + + return flecs::column<A>(static_cast<A*>(ecs_iter_column_w_size(m_iter, + sizeof(A), col)), static_cast<std::size_t>(m_iter->count), false); + } + + template <typename T> + flecs::column<T> table_column(flecs::id_t obj) const { + auto col = ecs_iter_find_column(m_iter, + ecs_pair(_::cpp_type<T>::id(), obj)); + ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); + + return flecs::column<T>(static_cast<T*>(ecs_iter_column_w_size(m_iter, + sizeof(T), col)), static_cast<std::size_t>(m_iter->count), false); + } + +private: + /* Get term, check if correct type is used */ + template <typename T, typename A = actual_type_t<T>> + flecs::column<T> get_term(int32_t index) const { + +#ifndef NDEBUG + ecs_entity_t term_id = ecs_term_id(m_iter, index); + ecs_assert(term_id & ECS_PAIR || term_id & ECS_SWITCH || + term_id & ECS_CASE || + term_id == _::cpp_type<T>::id(m_iter->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif + + size_t count; + bool is_shared = !ecs_term_is_owned(m_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<size_t>(m_iter->count); + } + + return flecs::column<A>( + static_cast<T*>(ecs_term_w_size(m_iter, sizeof(A), index)), + count, is_shared); + } + + flecs::unsafe_column get_unsafe_term(int32_t index) const { + size_t count; + size_t size = ecs_term_size(m_iter, index); + bool is_shared = !ecs_term_is_owned(m_iter, index); + + /* 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<size_t>(m_iter->count); + } + + return flecs::unsafe_column( + ecs_term_w_size(m_iter, 0, index), size, count, is_shared); + } + + const flecs::iter_t *m_iter; + std::size_t m_begin; + std::size_t m_end; +}; + +} // namespace flecs + +namespace flecs +{ + +/** Static helper functions to assign a component value */ + +// set(T&&), T = constructible +template <typename T, if_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, NULL)); + dst = std::move(value); + + ecs_modified_id(world, entity, id); +} + +// set(const T&), T = constructible +template <typename T, if_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, entity_t entity, const T& value, ecs_id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, NULL)); + dst = value; + + ecs_modified_id(world, entity, id); +} + +// set(T&&), T = not constructible +template <typename T, if_not_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + bool is_new = false; + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, &is_new)); + + /* If type is not constructible get_mut should assert on new values */ + ecs_assert(!is_new, ECS_INTERNAL_ERROR, NULL); + + dst = std::move(value); + + ecs_modified_id(world, entity, id); +} + +// set(const T&), T = not constructible +template <typename T, if_not_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, id_t entity, const T& value, id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + bool is_new = false; + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, &is_new)); + + /* If type is not constructible get_mut should assert on new values */ + ecs_assert(!is_new, ECS_INTERNAL_ERROR, NULL); + dst = value; + + ecs_modified_id(world, entity, id); +} + +// emplace for T(Args...) +template <typename T, typename ... Args, if_t< + std::is_constructible<actual_type_t<T>, Args...>::value || + std::is_default_constructible<actual_type_t<T>>::value > = 0> +inline void emplace(world_t *world, id_t entity, Args&&... args) { + id_t id = _::cpp_type<T>::id(world); + + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + T& dst = *static_cast<T*>(ecs_emplace_id(world, entity, id)); + + FLECS_PLACEMENT_NEW(&dst, T{std::forward<Args>(args)...}); + + ecs_modified_id(world, entity, id); +} + +// emplace for T(flecs::entity, Args...) +template <typename T, typename ... Args, if_t< + std::is_constructible<actual_type_t<T>, flecs::entity, Args...>::value > = 0> +inline void emplace(world_t *world, id_t entity, Args&&... args); + +// set(T&&) +template <typename T, typename A> +inline void set(world_t *world, entity_t entity, A&& value) { + id_t id = _::cpp_type<T>::id(world); + flecs::set(world, entity, std::forward<A&&>(value), id); +} + +// set(const T&) +template <typename T, typename A> +inline void set(world_t *world, entity_t entity, const A& value) { + id_t id = _::cpp_type<T>::id(world); + flecs::set(world, entity, value, id); +} + + +/** 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. + */ +class world final { +public: + /** Create world. + */ + explicit world() + : m_world( ecs_init() ) + , m_owned( true ) { 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[]) + : m_world( ecs_init_w_args(argc, argv) ) + , m_owned( true ) { init_builtin_components(); } + + /** Create world from C world. + */ + explicit world(world_t *w) + : m_world( w ) + , m_owned( false ) { } + + /** Not allowed to copy a world. May only take a reference. + */ + world(const world& obj) = delete; + + world(world&& obj) { + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + } + + /* Implicit conversion to world_t* */ + operator world_t*() const { return m_world; } + + /** Not allowed to copy a world. May only take a reference. + */ + world& operator=(const world& obj) = delete; + + world& operator=(world&& obj) { + this->~world(); + + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + return *this; + } + + ~world() { + if (m_owned && ecs_stage_is_async(m_world)) { + ecs_async_stage_free(m_world); + } else + if (m_owned && m_world) { + ecs_fini(m_world); + } + } + + /** Obtain pointer to C world object. + */ + world_t* c_ptr() const { + return m_world; + } + + /** Enable tracing. + * + * @param level The tracing level. + */ + static void enable_tracing(int level) { + ecs_tracing_enable(level); + } + + /** Enable tracing with colors. + * + * @param enabled Whether to enable tracing with colors. + */ + static void enable_tracing_color(bool enabled) { + ecs_tracing_color_enable(enabled); + } + + void set_pipeline(const flecs::pipeline& pip) const; + + /** Progress world, run all systems. + * + * @param delta_time Custom delta_time. If 0 is provided, Flecs will automatically measure delta_time. + */ + bool progress(FLECS_FLOAT delta_time = 0.0) const { + return ecs_progress(m_world, delta_time); + } + + /** Get last delta_time. + */ + FLECS_FLOAT delta_time() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->delta_time; + } + + /** Signal application should quit. + * After calling this operation, the next call to progress() returns false. + */ + void quit() { + ecs_quit(m_world); + } + + /** Test if quit() has been called. + */ + bool should_quit() { + return ecs_should_quit(m_world); + } + + /** Get id from a type. + */ + template <typename T> + flecs::id id() const; + + /** Id factory. + */ + template <typename ... Args> + flecs::id id(Args&&... args) const; + + /** Get pair id from relation, object + */ + template <typename R, typename O> + flecs::id pair() const; + + /** Get pair id from relation, object + */ + template <typename R> + flecs::id pair(entity_t o) const; + + /** Get pair id from relation, object + */ + flecs::id pair(entity_t r, entity_t o) const; + + /** 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. + */ + FLECS_FLOAT frame_begin(float delta_time = 0) { + return ecs_frame_begin(m_world, delta_time); + } + + /** End frame. + * This operation must be called at the end of the frame, and always after + * ecs_frame_begin. + * + * This function should only be ran from the main thread. + */ + void frame_end() { + ecs_frame_end(m_world); + } + + /** Begin staging. + * When an application does not use ecs_progress to control the main loop, it + * can still use Flecs features such as the defer queue. When an application + * needs to stage changes, it needs to call this function after ecs_frame_begin. + * A call to ecs_staging_begin must be followed by a call to ecs_staging_end. + * + * When staging is enabled, modifications to entities are stored to a stage. + * This ensures that arrays are not modified while iterating. Modifications are + * merged back to the "main stage" when ecs_staging_end is invoked. + * + * While the world is in staging mode, no structural changes (add/remove/...) + * can be made to the world itself. Operations must be executed on a stage + * instead (see ecs_get_stage). + * + * This function should only be ran from the main thread. + * + * @return Whether world is currently staged. + */ + bool staging_begin() { + return ecs_staging_begin(m_world); + } + + /** End staging. + * Leaves staging mode. After this operation the world may be directly mutated + * again. By default this operation also merges data back into the world, unless + * automerging was disabled explicitly. + * + * This function should only be ran from the main thread. + */ + void staging_end() { + ecs_staging_end(m_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. + */ + bool defer_begin() { + return ecs_defer_begin(m_world); + } + + /** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + */ + bool defer_end() { + return ecs_defer_end(m_world); + } + + /** Test whether deferring is enabled. + */ + bool is_deferred() { + return ecs_is_deferred(m_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_stages() operation is useful for applications that want to manage + * their own stages and/or threads. + * + * @param stages The number of stages. + */ + void set_stages(int32_t stages) const { + ecs_set_stages(m_world, stages); + } + + /** Get number of configured stages. + * Return number of stages set by set_stages. + * + * @return The number of stages used for threading. + */ + int32_t get_stage_count() const { + return ecs_get_stage_count(m_world); + } + + /** 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_get_stage_id(m_world); + } + + /** Enable/disable automerging for world or stage. + * When automerging is enabled, staged data will automatically be merged + * with the world when staging ends. This happens at the end of progress(), + * at a sync point or when staging_end() is called. + * + * Applications can exercise more control over when data from a stage is + * merged by disabling automerging. This requires an application to + * explicitly call merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this + * function is invoked on a stage, automerging is only set for that specific + * stage. + * + * @param automerge Whether to enable or disable automerging. + */ + void set_automerge(bool automerge) { + ecs_set_automerge(m_world, automerge); + } + + /** Merge world or stage. + * When automatic merging is disabled, an application can call this + * operation on either an individual stage, or on the world which will merge + * all stages. This operation may only be called when staging is not enabled + * (either after progress() or after staging_end()). + * + * This operation may be called on an already merged stage or world. + */ + void merge() { + ecs_merge(m_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(m_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. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * + * @return The stage. + */ + flecs::world async_stage() const { + auto result = flecs::world(ecs_async_stage_new(m_world)); + result.m_owned = true; + return result; + } + + /** 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( + m_world ? const_cast<flecs::world_t*>(ecs_get_world(m_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. + */ + bool is_readonly() const { + return ecs_stage_is_readonly(m_world); + } + + /** Set number of threads. + * This will distribute the load evenly across the configured number of + * threads for each system. + * + * @param threads Number of threads. + */ + void set_threads(int32_t threads) const { + ecs_set_threads(m_world, threads); + } + + /** Get number of threads. + * + * @return Number of configured threads. + */ + int32_t get_threads() const { + return ecs_get_threads(m_world); + } + + /** Get index of current thread. + * + * @return Unique index for current thread. + */ + ECS_DEPRECATED("use get_stage_id") + int32_t get_thread_index() const { + return ecs_get_stage_id(m_world); + } + + /** Set target FPS + * This will ensure that the main loop (world::progress) does not run faster + * than the specified frames per second. + * + * @param target_fps Target frames per second. + */ + void set_target_fps(FLECS_FLOAT target_fps) const { + ecs_set_target_fps(m_world, target_fps); + } + + /** Get target FPS + * + * @return Configured frames per second. + */ + FLECS_FLOAT get_target_fps() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->target_fps; + } + + /** Get tick + * + * @return Monotonically increasing frame count. + */ + int32_t get_tick() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->frame_count_total; + } + + /** Set timescale + * + * @return Monotonically increasing frame count. + */ + void set_time_scale(FLECS_FLOAT mul) const { + ecs_set_time_scale(m_world, mul); + } + + /** Get timescale + * + * @return Monotonically increasing frame count. + */ + FLECS_FLOAT get_time_scale() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->time_scale; + } + + /** Set world context. + * Set a context value that can be accessed by anyone that has a reference + * to the world. + * + * @param ctx The world context. + */ + void set_context(void* ctx) const { + ecs_set_context(m_world, ctx); + } + + /** Get world context. + * + * @return The configured world context. + */ + void* get_context() const { + return ecs_get_context(m_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. + */ + void dim(int32_t entity_count) const { + ecs_dim(m_world, entity_count); + } + + /** Preallocate memory for type + * This function preallocates memory for the component arrays of the + * specified type. + * + * @param type Type to preallocate memory for. + * @param entity_count Number of entities to preallocate memory for. + */ + void dim_type(type_t t, int32_t entity_count) const { + ecs_dim_type(m_world, t, 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. + */ + void set_entity_range(entity_t min, entity_t max) const { + ecs_set_entity_range(m_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. + */ + void enable_range_check(bool enabled) const { + ecs_enable_range_check(m_world, enabled); + } + + /** Disables inactive systems. + * + * This removes systems that are not matched with any entities from the main + * loop. Systems are only added to the main loop after they first match with + * entities, but are not removed automatically. + * + * This function allows an application to manually disable inactive systems + * which removes them from the main loop. Doing so will cause Flecs to + * rebuild the pipeline in the next iteration. + * + * @param level The tracing level. + */ + void deactivate_systems() { + ecs_deactivate_systems(m_world); + } + + /** Set current scope. + * + * @param scope The scope to set. + * @return The current scope; + */ + flecs::entity set_scope(const flecs::entity& scope) const; + + /** Get current scope. + * + * @return The current scope. + */ + flecs::entity get_scope() const; + + /** Lookup entity by name. + * + * @param name Entity name. + */ + flecs::entity lookup(const char *name) const; + + /** Set singleton component. + */ + template <typename T> + void set(const T& value) const { + flecs::set<T>(m_world, _::cpp_type<T>::id(m_world), value); + } + + template <typename T> + void set(T&& value) const { + flecs::set<T>(m_world, _::cpp_type<T>::id(m_world), + std::forward<T&&>(value)); + } + + template <typename T, typename ... Args> + void emplace(Args&&... args) const { + flecs::emplace<T>(m_world, _::cpp_type<T>::id(m_world), + std::forward<Args>(args)...); + } + + /** Get mut singleton component. + */ + template <typename T> + T* get_mut() const; + + /** Mark singleton component as modified. + */ + template <typename T> + void modified() const; + + /** Patch singleton component. + */ + template <typename T, typename Func> + void patch(const Func& func) const; + + /** Get singleton component. + */ + template <typename T> + const T* get() const; + + /** Test if world has singleton component. + */ + template <typename T> + bool has() const; + + /** Add singleton component. + */ + template <typename T> + void add() const; + + /** Remove singleton component. + */ + template <typename T> + void remove() const; + + /** Get id for type. + */ + template <typename T> + entity_t type_id() { + return _::cpp_type<T>::id(m_world); + } + + /** Get singleton entity for type. + */ + template <typename T> + flecs::entity singleton(); + + /** Create alias for component. + * + * @tparam Component to create an alias for. + * @param alias Alias for the component. + */ + template <typename T> + flecs::entity use(const char *alias = nullptr); + + /** 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); + + /** 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); + + /** Count entities matching a component. + * + * @tparam T The component to use for matching. + */ + template <typename T> + int count() const { + return ecs_count_id(m_world, _::cpp_type<T>::id(m_world)); + } + + flecs::filter_iterator begin() const; + flecs::filter_iterator end() const; + + /** Enable locking. + * + * @param enabled True if locking should be enabled, false if not. + */ + bool enable_locking(bool enabled) { + return ecs_enable_locking(m_world, enabled); + } + + /** Lock world. + */ + void lock() { + ecs_lock(m_world); + } + + /** Unlock world. + */ + void unlock() { + ecs_unlock(m_world); + } + + /** All entities created in function are created with id. + */ + template <typename Func> + void with(id_t with_id, const Func& func) const { + ecs_id_t prev = ecs_set_with(m_world, with_id); + func(); + ecs_set_with(m_world, prev); + } + + /** All entities created in function are created with type. + */ + template <typename T, typename Func> + void with(const Func& func) const { + with(this->id<T>(), func); + } + + /** All entities created in function are created with relation. + */ + template <typename Relation, typename Object, typename Func> + void with(const Func& func) const { + with(ecs_pair(this->id<Relation>(), this->id<Object>()), func); + } + + /** All entities created in function are created with relation. + */ + template <typename Relation, typename Func> + void with(id_t object, const Func& func) const { + with(ecs_pair(this->id<Relation>(), object), func); + } + + /** All entities created in function are created with relation. + */ + template <typename Func> + void with(id_t relation, id_t object, const Func& func) const { + with(ecs_pair(relation, object), func); + } + + /** All entities created in function are created in scope. All operations + * called in function (such as lookup) are relative to scope. + */ + template <typename Func> + void scope(id_t parent, const Func& func) const { + ecs_entity_t prev = ecs_set_scope(m_world, parent); + func(); + ecs_set_scope(m_world, prev); + } + + /** Defer all operations called in function. If the world is already in + * deferred mode, do nothing. + */ + template <typename Func> + void defer(const Func& func) const { + ecs_defer_begin(m_world); + func(); + ecs_defer_end(m_world); + } + + /** Iterate over all entities with provided component. + * The function parameter must match the following signature: + * void(*)(T&) or + * void(*)(flecs::entity, T&) + */ + template <typename T, typename Func> + void each(Func&& func) const; + + /** Iterate over all entities with provided (component) id. + */ + template <typename Func> + void each(flecs::id_t term_id, Func&& func) const; + + /** Iterate over all entities with components in argument list of function. + * The function parameter must match the following signature: + * void(*)(T&, U&, ...) or + * void(*)(flecs::entity, T&, U&, ...) + */ + template <typename Func> + void each(Func&& func) const; + + /** Create a prefab. + */ + template <typename... Args> + flecs::entity entity(Args &&... args) const; + + /** Create an entity. + */ + template <typename... Args> + flecs::entity prefab(Args &&... args) const; + + /** Create a type. + */ + template <typename... Args> + flecs::type type(Args &&... args) const; + + /** Create a pipeline. + */ + template <typename... Args> + flecs::pipeline pipeline(Args &&... args) const; + + /** Create a module. + */ + template <typename Module, typename... Args> + flecs::entity module(Args &&... args) const; + + /** Import a module. + */ + template <typename Module> + flecs::entity import(); // Cannot be const because modules accept a non-const world + + /** Create a system from an entity + */ + flecs::system<> system(flecs::entity e) const; + + /** Create a system. + */ + template <typename... Comps, typename... Args> + flecs::system_builder<Comps...> system(Args &&... args) const; + + /** Create an observer. + */ + template <typename... Comps, typename... Args> + flecs::observer_builder<Comps...> observer(Args &&... args) const; + + /** Create a filter. + */ + template <typename... Comps, typename... Args> + flecs::filter<Comps...> filter(Args &&... args) const; + + /** Create a filter builder. + */ + template <typename... Comps, typename... Args> + flecs::filter_builder<Comps...> filter_builder(Args &&... args) const; + + /** Create a query. + */ + template <typename... Comps, typename... Args> + flecs::query<Comps...> query(Args &&... args) const; + + /** Create a query builder. + */ + template <typename... Comps, typename... Args> + flecs::query_builder<Comps...> query_builder(Args &&... args) const; + + /** Create a term + */ + template<typename... Args> + flecs::term term(Args &&... args) const; + + /** Create a term for a type + */ + template<typename T, typename... Args> + flecs::term term(Args &&... args) const; + + /** Create a term for a pair + */ + template<typename R, typename O, typename... Args> + flecs::term term(Args &&... args) const; + + /** Register a component. + */ + template <typename T, typename... Args> + flecs::entity component(Args &&... args) const; + + /** Register a POD component. + */ + template <typename T, typename... Args> + flecs::entity pod_component(Args &&... args) const; + + /** Register a relocatable component. + */ + template <typename T, typename... Args> + flecs::entity relocatable_component(Args &&... args) const; + + /** Create a snapshot. + */ + template <typename... Args> + flecs::snapshot snapshot(Args &&... args) const; + +private: + void init_builtin_components(); + + world_t *m_world; + bool m_owned; +}; + +// Downcast utility to make world available to classes in inheritance hierarchy +template<typename Base> +class world_base { +public: + template<typename IBuilder> + static flecs::world world(const IBuilder *self) { + return flecs::world(static_cast<const Base*>(self)->m_world); + } + + flecs::world world() const { + return flecs::world(static_cast<const Base*>(this)->m_world); + } +}; + +} // namespace flecs + +namespace flecs { + +/** Class that stores a flecs id. + * A flecs id is an identifier that can store an entity id, an relation-object + * pair, or role annotated id (such as SWITCH | Movement). + */ +class id : public world_base<id> { +public: + explicit id(flecs::id_t value = 0) + : m_world(nullptr) + , m_id(value) { } + + explicit id(flecs::world_t *world, flecs::id_t value = 0) + : m_world(world) + , m_id(value) { } + + explicit id(flecs::world_t *world, flecs::id_t relation, flecs::id_t object) + : m_world(world) + , m_id(ecs_pair(relation, object)) { } + + explicit id(flecs::id_t relation, flecs::id_t object) + : m_world(nullptr) + , m_id(ecs_pair(relation, object)) { } + + explicit id(const flecs::id& relation, const flecs::id& object) + : m_world(relation.world()) + , m_id(ecs_pair(relation.m_id, object.m_id)) { } + + /** Test if id is pair (has relation, object) */ + bool is_pair() const { + return (m_id & ECS_ROLE_MASK) == flecs::Pair; + } + + /* Test if id is a wildcard */ + bool is_wildcard() const { + return ecs_id_is_wildcard(m_id); + } + + /* Test if id has the Switch role */ + bool is_switch() const { + return (m_id & ECS_ROLE_MASK) == flecs::Switch; + } + + /* Test if id has the Case role */ + bool is_case() const { + return (m_id & ECS_ROLE_MASK) == flecs::Case; + } + + /* Return id as entity (only allowed when id is valid entity) */ + flecs::entity entity() const; + + /* Return id with role added */ + flecs::entity add_role(flecs::id_t role) const; + + /* Return id with role removed */ + flecs::entity remove_role(flecs::id_t role) const; + + /* Return id without role */ + flecs::entity remove_role() const; + + /* Return id without role */ + flecs::entity remove_generation() const; + + /* Test if id has specified role */ + bool has_role(flecs::id_t role) const { + return ((m_id & ECS_ROLE_MASK) == role); + } + + /* Test if id has any role */ + bool has_role() const { + return (m_id & ECS_ROLE_MASK) != 0; + } + + flecs::entity role() const; + + /* Test if id has specified relation */ + bool has_relation(flecs::id_t relation) const { + if (!is_pair()) { + return false; + } + return ECS_PAIR_RELATION(m_id) == relation; + } + + /** Get relation from 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 relation() const; + + /** Get object from 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 object() const; + + /* Convert id to string */ + flecs::string str() const { + size_t size = ecs_id_str(m_world, m_id, NULL, 0); + char *result = static_cast<char*>(ecs_os_malloc( + static_cast<ecs_size_t>(size) + 1)); + ecs_id_str(m_world, m_id, result, size + 1); + return flecs::string(result); + } + + /** Convert role of id to string. */ + flecs::string role_str() const { + return flecs::string_view( ecs_role_str(m_id & ECS_ROLE_MASK)); + } + + ECS_DEPRECATED("use object()") + flecs::entity lo() const; + + ECS_DEPRECATED("use relation()") + flecs::entity hi() const; + + ECS_DEPRECATED("use flecs::id(relation, object)") + static + flecs::entity comb(entity_view lo, entity_view hi); + + flecs::id_t raw_id() const { + return m_id; + } + + operator flecs::id_t() const { + return m_id; + } + + /* World is optional, but guarantees that entity identifiers extracted from + * the id are valid */ + flecs::world_t *m_world; + flecs::id_t m_id; +}; + +} + +namespace flecs { + +template<typename T, typename Base> +class entity_builder_base { +public: + const Base& base() const { return *static_cast<const Base*>(this); } + flecs::world_t* base_world() const { return base().world(); } + flecs::entity_t base_id() const { return base().id(); } + operator const Base&() const { + return this->base(); + } +}; + +} + +#ifdef FLECS_DEPRECATED + +namespace flecs +{ + +struct entity_builder_deprecated_tag { }; + +/** Deprecated functions */ +template <typename Base> +class entity_builder_deprecated : public entity_builder_base<entity_builder_deprecated_tag, Base> { +public: + template<typename T, typename C> + ECS_DEPRECATED("use add<Relation, Object>") + const Base& add_trait() const { + ecs_add_pair(this->base_world(), this->base_id(), + _::cpp_type<T>::id(this->base_world()), + _::cpp_type<C>::id(this->base_world())); + return *this; + } + + template<typename T> + ECS_DEPRECATED("use add<Relation>(const entity&)") + const Base& add_trait(const Base& c) const { + ecs_add_pair(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world()), c.id()); + return *this; + } + + template<typename C> + ECS_DEPRECATED("use add_object<Object>(const entity&)") + const Base& add_trait_tag(const Base& t) const { + ecs_add_pair(this->base_world(), this->base_id(), t.id(), _::cpp_type<C>::id(this->base_world())); + return *this; + } + + ECS_DEPRECATED("use add(const entity&, const entity&)") + const Base& add_trait(const Base& t, const Base& c) const { + ecs_add_pair(this->base_world(), this->base_id(), t.id(), c.id()); + return *this; + } + + template<typename T, typename C> + ECS_DEPRECATED("use remove<Relation, Object>") + const Base& remove_trait() const { + ecs_remove_pair(this->base_world(), this->base_id(), + _::cpp_type<T>::id(this->base_world()), + _::cpp_type<C>::id(this->base_world())); + return *this; + } + + template<typename T> + ECS_DEPRECATED("use remove<Relation>(const entity&)") + const Base& remove_trait(const Base& c) const { + ecs_remove_pair(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world()), c.id()); + return *this; + } + + template<typename C> + ECS_DEPRECATED("use remove_object<Object>(const entity&)") + const Base& remove_trait_tag(const Base& t) const { + ecs_remove_pair(this->base_world(), this->base_id(), t.id(), _::cpp_type<C>::id(this->base_world())); + return *this; + } + + ECS_DEPRECATED("use remove(const entity&, const entity&)") + const Base& remove_trait(const Base& t, const Base& c) const { + ecs_remove_pair(this->base_world(), this->base_id(), t.id(), c.id()); + return *this; + } + + template <typename T, typename C> + ECS_DEPRECATED("use set<Relation, Object>(const Relation&)") + const Base& set_trait(const T& value) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_set_ptr_w_entity(this->base_world(), this->base_id(), + ecs_pair(comp_id, _::cpp_type<C>::id(this->base_world())), + sizeof(T), &value); + + return *this; + } + + template <typename T> + ECS_DEPRECATED("use set<Relation>(const entity&, const Relation&)") + const Base& set_trait(const T& value, const Base& c) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_set_ptr_w_entity(this->base_world(), this->base_id(), + ecs_pair(comp_id, c.id()), + sizeof(T), &value); + + return *this; + } + + template <typename C> + ECS_DEPRECATED("use set_object<Object>(const entity&, const Object&)") + const Base& set_trait_tag(const Base& t, const C& value) const { + auto comp_id = _::cpp_type<C>::id(this->base_world()); + ecs_assert(_::cpp_type<C>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_set_ptr_w_entity(this->base_world(), this->base_id(), + ecs_pair(t.id(), comp_id), + sizeof(C), &value); + + return *this; + } + + ECS_DEPRECATED("use set(Func func)") + template <typename T, typename Func> + const Base& patch(const Func& func) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + + ecs_assert(_::cpp_type<T>::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + bool is_added; + T *ptr = static_cast<T*>(ecs_get_mut_w_entity( + this->base_world(), this->base_id(), comp_id, &is_added)); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + func(*ptr); + ecs_modified_w_entity(this->base_world(), this->base_id(), comp_id); + + return *this; + } +}; + +struct entity_deprecated_tag { }; + +template<typename Base> +class entity_deprecated : entity_builder_base<entity_deprecated_tag, Base> { +public: + template<typename T, typename C> + ECS_DEPRECATED("use get<Relation, Object>") + const T* get_trait() const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<const T*>(ecs_get_id(this->base_world(), this->base_id(), ecs_trait( + _::cpp_type<C>::id(this->base_world()), comp_id))); + } + + template<typename T> + ECS_DEPRECATED("use get<Relation>(const entity&)") + const T* get_trait(const Base& c) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<const T*>(ecs_get_id(this->base_world(), this->base_id(), ecs_trait( + c.id(), comp_id))); + } + + template<typename C> + ECS_DEPRECATED("use get_object<Object>(const entity&)") + const C* get_trait_tag(const Base& t) const { + auto comp_id = _::cpp_type<C>::id(this->base_world()); + ecs_assert(_::cpp_type<C>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<const C*>(ecs_get_id(this->base_world(), this->base_id(), ecs_trait( + comp_id, t.id()))); + } + + ECS_DEPRECATED("use get(const entity&, const entity&)") + const void* get_trait(const Base& t, const Base& c) const{ + return ecs_get_id(this->base_world(), this->base_id(), ecs_trait(c.id(), t.id())); + } + + template <typename T, typename C> + ECS_DEPRECATED("use get_mut<Relation, Object>(bool)") + T* get_trait_mut(bool *is_added = nullptr) const { + auto t_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<T*>( + ecs_get_mut_w_entity( + this->base_world(), this->base_id(), ecs_trait(_::cpp_type<C>::id(this->base_world()), + t_id), is_added)); + } + + template <typename T> + ECS_DEPRECATED("use get_mut<Relation>(const entity&, bool)") + T* get_trait_mut(const Base& c, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<T*>( + ecs_get_mut_w_entity( + this->base_world(), this->base_id(), ecs_trait( comp_id, c.id()), is_added)); + } + + template <typename C> + ECS_DEPRECATED("use get_mut_object<Object>(const entity&, bool)") + C* get_trait_tag_mut(const Base& t, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<C>::id(this->base_world()); + ecs_assert(_::cpp_type<C>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<C*>( + ecs_get_mut_w_entity( + this->base_world(), this->base_id(), ecs_trait(comp_id, t.id()), is_added)); + } + + template<typename T, typename C> + ECS_DEPRECATED("use has<Relation, Object>") + bool has_trait() const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + _::cpp_type<C>::id(this->base_world()), + _::cpp_type<T>::id(this->base_world()))); + } + + template<typename T> + ECS_DEPRECATED("use has<Relation>(const flecs::entity&)") + bool has_trait(const Base& component) const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + component.id(), _::cpp_type<T>::id(this->base_world()))); + } + + template<typename C> + ECS_DEPRECATED("use has_object<Object>(const flecs::entity&)") + bool has_trait_tag(const Base& trait) const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + _::cpp_type<C>::id(this->base_world()), trait.id())); + } + + ECS_DEPRECATED("use has(const flecs::entity&, const flecs::entity&)") + bool has_trait(const Base& trait, const Base& e) const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + e.id(), trait.id())); + } +}; + +} +#else +namespace flecs +{ +template <typename Base> +class entity_builder_deprecated { }; +class entity_deprecated { }; +} +#endif + +namespace flecs +{ + +/** Entity view class + * This class provides readonly access to entities. Using this class to store + * entities in components ensures valid handles, as this class will always store + * the actual world vs. a stage. The constructors of this class will never + * create a new entity. + * + * To obtain a mutable handle to the entity, use the "mut" function. + */ +class entity_view : public id { +public: + 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(const flecs::world& world, const entity_view& id) + : flecs::id( world.get_world(), id.id() ) { } + + /** Wrap an existing entity id. + * + * @param world Pointer to the world in which the entity is created. + * @param id The entity id. + */ + explicit entity_view(world_t *world, const entity_view& id) + : flecs::id( flecs::world(world).get_world(), id.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 m_id; + } + + /** Check is entity is valid. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_valid() const { + return m_world && ecs_is_valid(m_world, m_id); + } + + explicit operator bool() const { + return is_valid(); + } + + /** Check is entity is alive. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_alive() const { + return m_world && ecs_is_alive(m_world, m_id); + } + + /** Return the entity name. + * + * @return The entity name, or an empty string if the entity has no name. + */ + flecs::string_view name() const { + return flecs::string_view(ecs_get_name(m_world, m_id)); + } + + /** Return the entity path. + * + * @return The hierarchical entity path, or an empty string if the entity + * has no name. + */ + flecs::string path(const char *sep = "::", const char *init_sep = "::") const { + char *path = ecs_get_path_w_sep(m_world, 0, m_id, sep, init_sep); + return flecs::string(path); + } + + bool enabled() { + return !ecs_has_entity(m_world, m_id, flecs::Disabled); + } + + /** Return the type. + * + * @return Returns the entity type. + */ + flecs::type type() const; + + /** Return type containing the entity. + * + * @return A type that contains only this entity. + */ + flecs::type to_type() const; + + /** Iterate (component) ids of an entity. + * The function parameter must match the following signature: + * void(*)(flecs::id id) + * + * @param func The function invoked for each id. + */ + template <typename Func> + void each(const Func& func) const; + + /** Iterate objects for a given relationship. + * This operation will return the object for all ids that match with the + * (rel, *) pattern. + * + * The function parameter must match the following signature: + * void(*)(flecs::entity object) + * + * @param rel The relationship for which to iterate the objects. + * @param func The function invoked for each object. + */ + template <typename Func> + void each(const flecs::entity_view& rel, const Func& func) const; + + /** Iterate objects for a given relationship. + * This operation will return the object for all ids that match with the + * (Rel, *) pattern. + * + * The function parameter must match the following signature: + * void(*)(flecs::entity object) + * + * @tparam Rel The relationship for which to iterate the objects. + * @param func The function invoked for each object. + */ + template <typename Rel, typename Func> + void each(const Func& func) const { + return each(_::cpp_type<Rel>::id(m_world), func); + } + + /** Find all (component) ids contained by an entity matching a pattern. + * This operation will return all ids that match the provided pattern. The + * pattern may contain wildcards by using the flecs::Wildcard constant: + * + * match(flecs::Wildcard, ...) + * Matches with all non-pair ids. + * + * match(world.pair(rel, flecs::Wildcard)) + * Matches all pair ids with relationship rel + * + * match(world.pair(flecs::Wildcard, obj)) + * Matches all pair ids with object obj + * + * The function parameter must match the following signature: + * void(*)(flecs::id id) + * + * @param pattern The pattern to use for matching. + * @param func The function invoked for each matching id. + */ + template <typename Func> + void match(flecs::id_t pattern, const Func& func) const; + + /** 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 <typename T, if_t< is_actual<T>::value > = 0> + const T* get() const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const T*>(ecs_get_id(m_world, m_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 <typename T, typename A = actual_type_t<T>, + if_not_t< is_actual<T>::value > = 0> + const A* get() const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<A>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const A*>(ecs_get_id(m_world, m_id, comp_id)); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam R the relation type. + * @tparam O the object type. + */ + template <typename R, typename O, typename P = pair<R, O>, + typename A = actual_type_t<P>, if_not_t< flecs::is_pair<R>::value > = 0> + const A* get() const { + return this->get<P>(); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam R the relation type. + * @param object the object. + */ + template<typename R> + const R* get(const flecs::entity_view& object) const { + auto comp_id = _::cpp_type<R>::id(m_world); + ecs_assert(_::cpp_type<R>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const R*>( + ecs_get_id(m_world, m_id, ecs_pair(comp_id, object.id()))); + } + + /** Get component value (untyped). + * + * @param component The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + const void* get(const flecs::entity_view& component) const { + return ecs_get_id(m_world, m_id, component.id()); + } + + /** Get a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * relation nor the object part of the pair are components, the operation + * will fail. + * + * @param relation the relation. + * @param object the object. + */ + const void* get(const flecs::entity_view& relation, const flecs::entity_view& object) const { + return ecs_get_id(m_world, m_id, ecs_pair(relation.id(), object.id())); + } + + /** Get 1..N components. + * This operation accepts a callback with as arguments the components to + * retrieve. The callback will only be invoked when the entity has all + * the components. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. + * + * @param func The callback to invoke. + * @return True if the entity has all components, false if not. + */ + template <typename Func, if_t< is_callable<Func>::value > = 0> + bool get(const Func& func) const; + + /** Get the object part from a pair. + * This operation gets the value for a pair from the entity. The relation + * part of the pair should not be a component. + * + * @tparam O the object type. + * @param relation the relation. + */ + template<typename O> + const O* get_w_object(const flecs::entity_view& relation) const { + auto comp_id = _::cpp_type<O>::id(m_world); + ecs_assert(_::cpp_type<O>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const O*>( + ecs_get_id(m_world, m_id, ecs_pair(relation.id(), comp_id))); + } + + /** Get the object part from a pair. + * This operation gets the value for a pair from the entity. The relation + * part of the pair should not be a component. + * + * @tparam R the relation type. + * @tparam O the object type. + */ + template<typename R, typename O> + const O* get_w_object() const { + return get<pair_object<R, O>>(); + } + + /** Get object for a given relation. + * This operation returns the object for a given relation. The optional + * index can be used to iterate through objects, in case the entity has + * multiple instances for the same relation. + * + * @param relation The relation for which to retrieve the object. + * @param index The index (0 for the first instance of the relation). + */ + flecs::entity get_object(flecs::entity_t relation, int32_t index = 0) const; + + /** Get parent from an entity. + * This operation retrieves the parent entity that has the specified + * component. If no parent with the specified component is found, an entity + * with id 0 is returned. If multiple parents have the specified component, + * the operation returns the first encountered one. + * + * @tparam T The component for which to find the parent. + * @return The parent entity. + */ + template <typename T> + flecs::entity get_parent(); + + flecs::entity get_parent(flecs::entity_view e); + + /** Lookup an entity by name. + * Lookup an entity in the scope of this entity. The provided path may + * contain double colons as scope separators, for example: "Foo::Bar". + * + * @param path The name of the entity to lookup. + * @return The found entity, or entity::null if no entity matched. + */ + flecs::entity lookup(const char *path) const; + + /** Check if entity has the provided type. + * + * @param entity The type pointer to check. + * @return True if the entity has the provided type, false otherwise. + */ + bool has(type_t type) const { + return ecs_has_type(m_world, m_id, type); + } + + /** Check if entity has the provided entity. + * + * @param entity The entity to check. + * @return True if the entity has the provided entity, false otherwise. + */ + bool has(flecs::id_t e) const { + return ecs_has_id(m_world, m_id, e); + } + + /** 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 <typename T> + bool has() const { + return ecs_has_id(m_world, m_id, _::cpp_type<T>::id(m_world)); + } + + /** Check if entity has the provided pair. + * + * @tparam Relation The relation type. + * @param Object The object type. + * @return True if the entity has the provided component, false otherwise. + */ + template <typename Relation, typename Object> + bool has() const { + return this->has<Relation>(_::cpp_type<Object>::id(m_world)); + } + + /** Check if entity has the provided pair. + * + * @tparam Relation The relation type. + * @param object The object. + * @return True if the entity has the provided component, false otherwise. + */ + template <typename Relation> + bool has(flecs::id_t object) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + return ecs_has_id(m_world, m_id, ecs_pair(comp_id, object)); + } + + /** Check if entity has the provided pair. + * + * @param relation The relation. + * @param object The object. + * @return True if the entity has the provided component, false otherwise. + */ + bool has(flecs::id_t relation, flecs::id_t object) const { + return ecs_has_id(m_world, m_id, ecs_pair(relation, object)); + } + + /** Check if entity has the provided pair. + * + * @tparam Object The object type. + * @param relation The relation. + * @return True if the entity has the provided component, false otherwise. + */ + template <typename Object> + bool has_w_object(flecs::id_t relation) const { + auto comp_id = _::cpp_type<Object>::id(m_world); + return ecs_has_id(m_world, m_id, ecs_pair(relation, comp_id)); + } + + /** Check if entity owns the provided type. + * An type is owned if it is not shared from a base entity. + * + * @param type The type to check. + * @return True if the entity owns the provided type, false otherwise. + */ + bool owns(type_t type) const { + return ecs_type_owns_type( + m_world, ecs_get_type(m_world, m_id), type, true); + } + + /** Check if entity owns the provided entity. + * An entity is owned if it is not shared from a base entity. + * + * @param entity 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_entity(m_world, m_id, e, true); + } + + /** Check if entity owns the provided pair. + * + * @tparam Relation The relation type. + * @param object The object. + * @return True if the entity owns the provided component, false otherwise. + */ + template <typename Relation> + bool owns(flecs::id_t object) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + return owns(ecs_pair(comp_id, object)); + } + + /** Check if entity owns the provided pair. + * + * @param relation The relation. + * @param object The object. + * @return True if the entity owns the provided component, false otherwise. + */ + bool owns(flecs::id_t relation, flecs::id_t object) const { + return owns(ecs_pair(relation, object)); + } + + /** 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 <typename T> + bool owns() const { + return owns(_::cpp_type<T>::id(m_world)); + } + + /** Check if entity has the provided switch. + * + * @param sw The switch to check. + * @return True if the entity has the provided switch, false otherwise. + */ + bool has_switch(const flecs::type& sw) const; + + template <typename T> + bool has_switch() const { + return ecs_has_entity(m_world, m_id, + flecs::Switch | _::cpp_type<T>::id(m_world)); + } + + /** Check if entity has the provided case. + * + * @param sw_case The case to check. + * @return True if the entity has the provided case, false otherwise. + */ + bool has_case(flecs::id_t sw_case) const { + return ecs_has_entity(m_world, m_id, flecs::Case | sw_case); + } + + template<typename T> + bool has_case() const { + return this->has_case(_::cpp_type<T>::id(m_world)); + } + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + flecs::entity get_case(flecs::id_t sw) const; + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + template<typename T> + flecs::entity get_case() const; + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + flecs::entity get_case(const flecs::type& sw) const; + + /** Test if component is enabled. + * + * @tparam T The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + template<typename T> + bool is_enabled() { + return ecs_is_component_enabled_w_entity( + m_world, m_id, _::cpp_type<T>::id(m_world)); + } + + /** Test if component is enabled. + * + * @param entity The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + bool is_enabled(const flecs::entity_view& e) { + return ecs_is_component_enabled_w_entity( + m_world, m_id, e.id()); + } + + /** Get current delta time. + * Convenience function so system implementations can get delta_time, even + * if they are using the .each() function. + * + * @return Current delta_time. + */ + FLECS_FLOAT delta_time() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->delta_time; + } + + /** Return iterator to entity children. + * Enables depth-first iteration over entity children. + * + * @return Iterator to child entities. + */ + child_iterator children() 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 stage An created for the current 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 stage An created for the current stage. + * @return An entity handle that allows for mutations in the current stage. + */ + flecs::entity mut(const flecs::entity_view& e) const; + +private: + flecs::entity set_stage(world_t *stage); +}; + +/** Fluent API for chaining entity operations + * This class contains entity operations that can be chained. For example, by + * using this class, an entity can be created like this: + * + * flecs::entity e = flecs::entity(world) + * .add<Position>() + * .add<Velocity>(); + */ +struct entity_builder_tag { }; // Tag to prevent ambiguous base + +template <typename Base> +class entity_builder : public entity_builder_base<entity_builder_tag, Base> { +public: + /** 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 <typename T> + const Base& add() const { + flecs_static_assert(is_flecs_constructible<T>::value, + "cannot default construct type: add T::T() or use emplace<T>()"); + ecs_add_id(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world())); + return *this; + } + + /** Add an entity to an entity. + * Add an entity to the entity. This is typically used for tagging. + * + * @param entity The entity to add. + */ + const Base& add(entity_t entity) const { + ecs_add_id(this->base_world(), this->base_id(), entity); + return *this; + } + + /** Add a type to an entity. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type The type to add. + */ + const Base& add(const type& type) const; + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @param relation The relation id. + * @param object The object id. + */ + const Base& add(entity_t relation, entity_t object) const { + ecs_add_pair(this->base_world(), this->base_id(), relation, object); + return *this; + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @tparam R the relation type. + * @tparam O the object type. + */ + template<typename R, typename O> + const Base& add() const { + return this->add<R>(_::cpp_type<O>::id(this->base_world())); + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @tparam R the relation type. + * @param object the object type. + */ + template<typename R> + const Base& add(entity_t object) const { + flecs_static_assert(is_flecs_constructible<R>::value, + "cannot default construct type: add T::T() or use emplace<T>()"); + return this->add(_::cpp_type<R>::id(this->base_world()), object); + } + + /** Shortcut for add(IsA. obj). + * + * @param object the object id. + */ + const Base& is_a(entity_t object) const { + return this->add(flecs::IsA, object); + } + + template <typename T> + const Base& is_a() const { + return this->add(flecs::IsA, _::cpp_type<T>::id(this->base_world())); + } + + /** Shortcut for add(ChildOf. obj). + * + * @param object the object id. + */ + const Base& child_of(entity_t object) const { + return this->add(flecs::ChildOf, object); + } + + /** Shortcut for add(ChildOf. obj). + * + * @param object the object id. + */ + template <typename T> + const Base& child_of() const { + return this->add(flecs::ChildOf, _::cpp_type<T>::id(this->base_world())); + } + + /** Add a pair with object type. + * This operation adds a pair to the entity. The relation part of the pair + * should not be a component. + * + * @param relation the relation type. + * @tparam O the object type. + */ + template<typename O> + const Base& add_w_object(entity_t relation) const { + flecs_static_assert(is_flecs_constructible<O>::value, + "cannot default construct type: add T::T() or use emplace<T>()"); + return this->add(relation, _::cpp_type<O>::id(this->base_world())); + } + + /** Remove a component from an entity. + * + * @tparam T the type of the component to remove. + */ + template <typename T> + const Base& remove() const { + ecs_remove_id(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world())); + return *this; + } + + /** Remove an entity from an entity. + * + * @param entity The entity to remove. + */ + const Base& remove(entity_t entity) const { + ecs_remove_id(this->base_world(), this->base_id(), entity); + return *this; + } + + /** Remove a type from an entity. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type the type to remove. + */ + const Base& remove(const type& type) const; + + /** Remove a pair. + * This operation removes a pair from the entity. + * + * @param relation The relation id. + * @param object The object id. + */ + const Base& remove(entity_t relation, entity_t object) const { + ecs_remove_pair(this->base_world(), this->base_id(), relation, object); + return *this; + } + + /** Removes a pair. + * This operation removes a pair from the entity. + * + * @tparam Relation the relation type. + * @tparam Object the object type. + */ + template<typename Relation, typename Object> + const Base& remove() const { + return this->remove<Relation>(_::cpp_type<Object>::id(this->base_world())); + } + + /** Remove a pair. + * This operation adds a pair to the entity. + * + * @tparam Relation the relation type. + * @param object the object type. + */ + template<typename Relation> + const Base& remove(entity_t object) const { + return this->remove(_::cpp_type<Relation>::id(this->base_world()), object); + } + + /** Removes a pair with object type. + * This operation removes a pair from the entity. + * + * @param relation the relation type. + * @tparam Object the object type. + */ + template<typename Object> + const Base& remove_w_object(entity_t relation) const { + return this->remove(relation, _::cpp_type<Object>::id(this->base_world())); + } + + /** Add owned flag for component (forces ownership when instantiating) + * + * @param entity The entity for which to add the OWNED flag + */ + const Base& add_owned(entity_t entity) const { + ecs_add_id(this->base_world(), this->base_id(), ECS_OWNED | entity); + return *this; + } + + /** Add owned flag for component (forces ownership when instantiating) + * + * @tparam T The component for which to add the OWNED flag + */ + template <typename T> + const Base& add_owned() const { + ecs_add_id(this->base_world(), this->base_id(), ECS_OWNED | _::cpp_type<T>::id(this->base_world())); + return *this; + } + + ECS_DEPRECATED("use add_owned(flecs::entity e)") + const Base& add_owned(const type& type) const; + + /** Set value, add owned flag. + * + * @tparam T The component to set and for which to add the OWNED flag + */ + template <typename T> + const Base& set_owned(T&& val) const { + this->add_owned<T>(); + this->set<T>(std::forward<T>(val)); + return *this; + } + + /** Add a switch to an entity by id. + * The switch entity must be a type, that is it must have the EcsType + * component. Entities created with flecs::type are valid here. + * + * @param sw The switch entity id to add. + */ + const Base& add_switch(entity_t sw) const { + ecs_add_id(this->base_world(), this->base_id(), ECS_SWITCH | sw); + return *this; + } + + /** Add a switch to an entity by C++ type. + * The C++ type must be associated with a switch type. + * + * @param sw The switch to add. + */ + template <typename T> + const Base& add_switch() const { + ecs_add_id(this->base_world(), this->base_id(), + ECS_SWITCH | _::cpp_type<T>::id()); + return *this; + } + + /** Add a switch to an entity. + * Any instance of flecs::type can be used as a switch. + * + * @param sw The switch to add. + */ + const Base& add_switch(const type& sw) const; + + /** Remove a switch from an entity by id. + * + * @param sw The switch entity id to remove. + */ + const Base& remove_switch(entity_t sw) const { + ecs_remove_id(this->base_world(), this->base_id(), ECS_SWITCH | sw); + return *this; + } + + /** Add a switch to an entity by C++ type. + * The C++ type must be associated with a switch type. + * + * @param sw The switch to add. + */ + template <typename T> + const Base& remove_switch() const { + ecs_remove_id(this->base_world(), this->base_id(), + ECS_SWITCH | _::cpp_type<T>::id()); + return *this; + } + + /** Remove a switch from an entity. + * Any instance of flecs::type can be used as a switch. + * + * @param sw The switch to remove. + */ + const Base& remove_switch(const type& sw) const; + + /** Add a switch to an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to add. + */ + const Base& add_case(entity_t sw_case) const { + ecs_add_id(this->base_world(), this->base_id(), ECS_CASE | sw_case); + return *this; + } + + /** Add a switch to an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @tparam T The case to add. + */ + template<typename T> + const Base& add_case() const { + return this->add_case(_::cpp_type<T>::id()); + } + + /** Remove a case from an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to remove. + */ + const Base& remove_case(entity_t sw_case) const { + ecs_remove_id(this->base_world(), this->base_id(), ECS_CASE | sw_case); + return *this; + } + + /** Remove a switch from an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @tparam T The case to remove. + */ + template<typename T> + const Base& remove_case() const { + return this->remove_case(_::cpp_type<T>::id()); + } + + /** Enable an entity. + * Enabled entities are matched with systems and can be searched with + * queries. + */ + const Base& enable() const { + ecs_enable(this->base_world(), this->base_id(), true); + return *this; + } + + /** Disable an entity. + * Disabled entities are not matched with systems and cannot be searched + * with queries, unless explicitly specified in the query expression. + */ + const Base& disable() const { + ecs_enable(this->base_world(), this->base_id(), false); + return *this; + } + + /** Enable a component. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @tparam T The component to enable. + */ + template<typename T> + const Base& enable() const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), _::cpp_type<T>::id(), true); + return *this; + } + + /** Disable a component. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @tparam T The component to enable. + */ + template<typename T> + const Base& disable() const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), _::cpp_type<T>::id(), false); + return *this; + } + + /** Enable a component. + * See enable<T>. + * + * @param component The component to enable. + */ + const Base& enable(entity_t comp) const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), comp, true); + return *this; + } + + /** Disable a component. + * See disable<T>. + * + * @param component The component to disable. + */ + const Base& disable(entity_t comp) const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), comp, false); + return *this; + } + + template<typename T, if_t< + !is_callable<T>::value && is_actual<T>::value> = 0 > + const Base& set(T&& value) const { + flecs::set<T>(this->base_world(), this->base_id(), std::forward<T&&>(value)); + return *this; + } + + template<typename T, if_t< + !is_callable<T>::value && is_actual<T>::value > = 0> + const Base& set(const T& value) const { + flecs::set<T>(this->base_world(), this->base_id(), value); + return *this; + } + + template<typename T, typename A = actual_type_t<T>, if_not_t< + is_callable<T>::value || is_actual<T>::value > = 0> + const Base& set(A&& value) const { + flecs::set<T>(this->base_world(), this->base_id(), std::forward<A&&>(value)); + return *this; + } + + template<typename T, typename A = actual_type_t<T>, if_not_t< + is_callable<T>::value || is_actual<T>::value > = 0> + const Base& set(const A& value) const { + flecs::set<T>(this->base_world(), this->base_id(), value); + return *this; + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses the relation as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam R The relation part of the pair. + * @tparam O The object part of the pair. + * @param value The value to set. + */ + template <typename R, typename O, typename P = pair<R, O>, + typename A = actual_type_t<P>, if_not_t< is_pair<R>::value> = 0> + const Base& set(const A& value) const { + flecs::set<P>(this->base_world(), this->base_id(), value); + return *this; + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses the relation as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam R The relation part of the pair. + * @param object The object part of the pair. + * @param value The value to set. + */ + template <typename R> + const Base& set(entity_t object, const R& value) const { + auto relation = _::cpp_type<R>::id(this->base_world()); + flecs::set(this->base_world(), this->base_id(), value, + ecs_pair(relation, object)); + return *this; + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses the relation as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Object The object part of the pair. + * @param relation The relation part of the pair. + * @param value The value to set. + */ + template <typename O> + const Base& set_w_object(entity_t relation, const O& value) const { + auto object = _::cpp_type<O>::id(this->base_world()); + flecs::set(this->base_world(), this->base_id(), value, + ecs_pair(relation, object)); + return *this; + } + + template <typename R, typename O> + const Base& set_w_object(const O& value) const { + flecs::set<pair_object<R, O>>(this->base_world(), this->base_id(), value); + return *this; + } + + /** Set 1..N components. + * This operation accepts a callback with as arguments the components to + * set. If the entity does not have all of the provided components, they + * will be added. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. When this operation is called + * while deferred, its performance is equivalent to that of calling get_mut + * for each component separately. + * + * The operation will invoke modified for each component after the callback + * has been invoked. + * + * @param func The callback to invoke. + */ + template <typename Func, if_t< is_callable<Func>::value > = 0> + const Base& set(const Func& func) const; + + /** Emplace component. + * Emplace constructs a component in the storage, which prevents calling the + * destructor on the object passed into the function. + * + * Emplace attempts the following signatures to construct the component: + * T{Args...} + * T{flecs::entity, Args...} + * + * If the second signature matches, emplace will pass in the current entity + * as argument to the constructor, which is useful if the component needs + * to be aware of the entity to which it has been added. + * + * Emplace may only be called for components that have not yet been added + * to the entity. + * + * @tparam T the component to emplace + * @param args The arguments to pass to the constructor of T + */ + template <typename T, typename ... Args> + const Base& emplace(Args&&... args) const { + flecs::emplace<T>(this->base_world(), this->base_id(), + std::forward<Args>(args)...); + return *this; + } + + /** Entities created in function will have the current entity. + * + * @param func The function to call. + */ + template <typename Func> + const Base& with(const Func& func) const { + ecs_id_t prev = ecs_set_with(this->base_world(), this->base_id()); + func(); + ecs_set_with(this->base_world(), prev); + return *this; + } + + /** Entities created in function will have (Relation, this) + * This operation is thread safe. + * + * @tparam Relation The relation to use. + * @param func The function to call. + */ + template <typename Relation, typename Func> + const Base& with(const Func& func) const { + with(_::cpp_type<Relation>::id(this->base_world()), func); + return *this; + } + + /** Entities created in function will have (relation, this) + * + * @param relation The relation to use. + * @param func The function to call. + */ + template <typename Func> + const Base& with(id_t relation, const Func& func) const { + ecs_id_t prev = ecs_set_with(this->base_world(), + ecs_pair(relation, this->base_id())); + func(); + ecs_set_with(this->base_world(), prev); + return *this; + } + + /** The function will be ran with the scope set to the current entity. */ + template <typename Func> + const Base& scope(const Func& func) const { + ecs_entity_t prev = ecs_set_scope(this->base_world(), this->base_id()); + func(); + ecs_set_scope(this->base_world(), prev); + return *this; + } + + /** Associate entity with type. + * This operation enables using a type to refer to an entity, as it + * associates the entity id with the provided type. + * + * If the entity does not have a name, a name will be derived from the type. + * If the entity already is a component, the provided type must match in + * size with the component size of the entity. After this operation the + * entity will be a component (it will have the EcsComponent component) if + * the type has a non-zero size. + * + * @tparam T the type to associate with the entity. + */ + template <typename T> + const Base& component() const; + + /* Set the entity name. + */ + const Base& set_name(const char *name) const { + ecs_set_name(this->base_world(), this->base_id(), name); + return *this; + } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Quick and safe access to a component pointer +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +class ref { +public: + ref() + : m_world( nullptr ) + , m_entity( 0 ) + , m_ref() { } + + ref(world_t *world, entity_t entity) + : m_world( world ) + , m_entity( entity ) + , m_ref() + { + auto comp_id = _::cpp_type<T>::id(world); + + ecs_assert(_::cpp_type<T>::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + ecs_get_ref_w_id( + m_world, &m_ref, m_entity, comp_id); + } + + const T* operator->() { + const T* result = static_cast<const T*>(ecs_get_ref_w_id( + m_world, &m_ref, m_entity, _::cpp_type<T>::id(m_world))); + + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); + + return result; + } + + const T* get() { + if (m_entity) { + ecs_get_ref_w_id( + m_world, &m_ref, m_entity, _::cpp_type<T>::id(m_world)); + } + + return static_cast<T*>(m_ref.ptr); + } + + flecs::entity entity() const; + +private: + world_t *m_world; + entity_t m_entity; + flecs::ref_t m_ref; +}; + +/** Entity class + * This class provides access to entities. */ +class entity : + public entity_view, + public entity_builder<entity>, + public entity_deprecated<entity>, + public entity_builder_deprecated<entity> +{ +public: + /** Default constructor. + */ + entity() + : flecs::entity_view() { } + + /** Create entity. + * + * @param world The world in which to create the entity. + */ + explicit entity(world_t *world) + : flecs::entity_view() + { + m_world = world; + m_id = ecs_new_w_type(world, 0); + } + + /** 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. + * @param is_component If true, the entity will be created from the pool of component ids (default = false). + */ + explicit entity(world_t *world, const char *name) + : flecs::entity_view() + { + m_world = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = "::"; + m_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(world_t *world, entity_t id) + : flecs::entity_view() + { + m_world = world; + m_id = id; + } + + /** Conversion from flecs::entity_t to flecs::entity. */ + explicit entity(entity_t id) + : flecs::entity_view( nullptr, id ) { } + + /** Get entity id. + * @return The integer entity id. + */ + entity_t id() const { + return m_id; + } + + /** Get mutable component value. + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. + * + * @tparam T The component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + template <typename T> + T* get_mut(bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<T*>( + ecs_get_mut_w_entity(m_world, m_id, comp_id, is_added)); + } + + /** 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 component The component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + void* get_mut(entity_t comp, bool *is_added = nullptr) const { + return ecs_get_mut_w_entity(m_world, m_id, comp, is_added); + } + + /** Get mutable pointer for a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Relation the relation type. + * @tparam Object the object type. + */ + template <typename Relation, typename Object> + Relation* get_mut(bool *is_added = nullptr) const { + return this->get_mut<Relation>( + _::cpp_type<Object>::id(m_world), is_added); + } + + /** Get mutable pointer for a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Relation the relation type. + * @param object the object. + */ + template <typename Relation> + Relation* get_mut(entity_t object, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + ecs_assert(_::cpp_type<Relation>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<Relation*>( + ecs_get_mut_w_entity(m_world, m_id, + ecs_pair(comp_id, object), is_added)); + } + + /** Get mutable pointer for a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * relation or object are a component, the operation will fail. + * + * @param relation the relation. + * @param object the object. + */ + void* get_mut(entity_t relation, entity_t object, bool *is_added = nullptr) const { + return ecs_get_mut_w_entity(m_world, m_id, + ecs_pair(relation, object), is_added); + } + + /** Get mutable pointer for the object from a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Object the object type. + * @param relation the relation. + */ + template <typename Object> + Object* get_mut_w_object(entity_t relation, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<Object>::id(m_world); + ecs_assert(_::cpp_type<Object>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<Object*>( + ecs_get_mut_w_entity(m_world, m_id, + ecs_pair(relation, comp_id), is_added)); + } + + /** Signal that component was modified. + * + * @tparam T component that was modified. + */ + template <typename T> + void modified() const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + this->modified(comp_id); + } + + /** Signal that the relation part of a pair was modified. + * + * @tparam Relation the relation type. + * @tparam Object the object type. + */ + template <typename Relation, typename Object> + void modified() const { + this->modified<Relation>(_::cpp_type<Object>::id(m_world)); + } + + /** Signal that the relation part of a pair was modified. + * + * @tparam Relation the relation type. + * @param object the object. + */ + template <typename Relation> + void modified(entity_t object) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + ecs_assert(_::cpp_type<Relation>::size() != 0, ECS_INVALID_PARAMETER, NULL); + this->modified(comp_id, object); + } + + /** Signal that a pair has modified (untyped). + * If neither the relation or object part of the pair are a component, the + * operation will fail. + * + * @param relation the relation. + * @param object the object. + */ + void modified(entity_t relation, entity_t object) const { + this->modified(ecs_pair(relation, object)); + } + + /** Signal that component was modified. + * + * @param component component that was modified. + */ + void modified(entity_t comp) const { + ecs_modified_w_entity(m_world, m_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 <typename T> + ref<T> get_ref() const { + // Ensure component is registered + _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return ref<T>(m_world, m_id); + } + + /** Clear an entity. + * This operation removes all components from an entity without recycling + * the entity id. + */ + void clear() const { + ecs_clear(m_world, m_id); + } + + /** Delete an entity. + * Entities have to be deleted explicitly, and are not deleted when the + * flecs::entity object goes out of scope. + */ + void destruct() const { + ecs_delete(m_world, m_id); + } + + /** Used by builder class. Do not invoke (deprecated). */ + template <typename Func> + void invoke(Func&& action) const { + action(m_world, m_id); + } + + /** Entity id 0. + * This function is useful when the API must provide an entity object that + * belongs to a world, but the entity id is 0. + * + * @param world The world. + */ + static + flecs::entity null(const flecs::world& world) { + return flecs::entity(world.get_world().c_ptr(), + static_cast<entity_t>(0)); + } + + static + flecs::entity null() { + return flecs::entity(static_cast<entity_t>(0)); + } +}; + +/** Prefab class */ +class prefab final : public entity { +public: + explicit prefab(world_t *world, const char *name = nullptr) + : entity(world, name) + { + this->add(flecs::Prefab); + } +}; + +} // namespace flecs +//////////////////////////////////////////////////////////////////////////////// +//// Register component, provide global access to component handles / metadata +//////////////////////////////////////////////////////////////////////////////// + +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. + // + struct name_util { + /* Remove parts from typename that aren't needed for component name */ + static void trim_name(char *typeName) { + ecs_size_t len = ecs_os_strlen(typeName); + + /* Remove 'const' */ + ecs_size_t const_len = ecs_os_strlen("const "); + if ((len > const_len) && !ecs_os_strncmp(typeName, "const ", const_len)) { + ecs_os_memmove(typeName, typeName + const_len, len - const_len); + typeName[len - const_len] = '\0'; + len -= const_len; + } + + /* Remove 'struct' */ + ecs_size_t struct_len = ecs_os_strlen("struct "); + if ((len > struct_len) && !ecs_os_strncmp(typeName, "struct ", struct_len)) { + ecs_os_memmove(typeName, typeName + struct_len, len - struct_len); + typeName[len - struct_len] = '\0'; + len -= struct_len; + } + + /* Remove 'class' */ + ecs_size_t class_len = ecs_os_strlen("class "); + if ((len > class_len) && !ecs_os_strncmp(typeName, "class ", class_len)) { + ecs_os_memmove(typeName, typeName + class_len, len - class_len); + typeName[len - class_len] = '\0'; + len -= class_len; + } + + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; + } + + /* Remove const at end of string */ + if (len > const_len) { + if (!ecs_os_strncmp(&typeName[len - const_len], " const", const_len)) { + typeName[len - const_len] = '\0'; + } + } + } + }; + +// Compiler-specific conversion from __PRETTY_FUNCTION__ to component name. +// This code uses a trick that instantiates a function for the component type. +// Then __PRETTY_FUNCTION__ is used to obtain the name of the function. Because +// the result of __PRETTY_FUNCTION__ is not standardized, there are different +// implementations for clang, gcc and msvc. Code that uses a different compiler +// needs to register component names explicitly. +#if defined(__clang__) + static const unsigned int FRONT_SIZE = sizeof("static const char* flecs::_::name_helper<") - 1u; + static const unsigned int BACK_SIZE = sizeof(">::name() [T = ]") - 1u; + + template <typename T> + struct name_helper + { + static const char* name(void) { + static const size_t size = (sizeof(__PRETTY_FUNCTION__) - FRONT_SIZE - BACK_SIZE) / 2 + 1u; + static char typeName[size + 6] = {}; + memcpy(typeName, __PRETTY_FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif defined(__GNUC__) + static const unsigned int FRONT_SIZE = sizeof("static const char* flecs::_::name_helper<T>::name() [with T = ") - 1u; + static const unsigned int BACK_SIZE = sizeof("]") - 1u; + + template <typename T> + struct name_helper + { + static const char* name(void) { + static const size_t size = sizeof(__PRETTY_FUNCTION__) - FRONT_SIZE - BACK_SIZE; + static char typeName[size + 6] = {}; + memcpy(typeName, __PRETTY_FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif defined(_WIN32) + static const unsigned int FRONT_SIZE = sizeof("flecs::_::name_helper<") - 1u; + static const unsigned int BACK_SIZE = sizeof(">::name") - 1u; + + template <typename T> + struct name_helper + { + static const char* name(void) { + static const size_t size = sizeof(__FUNCTION__) - FRONT_SIZE - BACK_SIZE; + static char typeName[size + 6] = {}; + memcpy(typeName, __FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#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 <typename T> +struct symbol_helper +{ + static char* symbol(void) { + const char *name = name_helper<T>::name(); + + // Symbol is same as name, but with '::' replaced with '.' + char *ptr, *sym = ecs_os_strdup(name); + ecs_size_t i, len = ecs_os_strlen(sym); + ptr = sym; + for (i = 0, ptr = sym; i < len && *ptr; i ++, ptr ++) { + if (*ptr == ':') { + sym[i] = '.'; + ptr ++; + } else { + sym[i] = *ptr; + } + } + + sym[i] = '\0'; + + return sym; + } +}; + +// 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<typename T, enable_if_t< + std::is_trivial<T>::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<typename T, enable_if_t< + std::is_trivial<T>::value == false + >* = nullptr> +void register_lifecycle_actions( + ecs_world_t *world, + ecs_entity_t component) +{ + if (!ecs_component_has_actions(world, component)) { + EcsComponentLifecycle cl{}; + cl.ctor = ctor<T>(); + cl.dtor = dtor<T>(); + + cl.copy = copy<T>(); + cl.copy_ctor = copy_ctor<T>(); + cl.move = move<T>(); + cl.move_ctor = move_ctor<T>(); + + cl.ctor_move_dtor = ctor_move_dtor<T>(); + cl.move_dtor = move_dtor<T>(); + + ecs_set_component_actions_w_entity( world, component, &cl); + } +} + +// Class that manages component ids across worlds & binaries. +// The cpp_type class stores the component id for a C++ type in a static global +// variable that is shared between worlds. Whenever a component is used this +// class will check if it already has been registered (has the global id been +// set), and if not, register the component with the world. +// +// If the id has been set, the class will ensure it is known by the world. If it +// is not known the component has been registered by another world and will be +// registered with the world using the same id. If the id does exist, the class +// will register it as a component, and verify whether the input is consistent. +template <typename T> +class cpp_type_size { +public: + static size_t size(bool allow_tag) { + // C++ types that have no members still have a size. Use std::is_empty + // to check if the type is empty. If so, use 0 for the component size. + // + // If s_allow_tag is set to false, the size returned by C++ is used. + // This is useful in cases where class instances are still required, as + // is the case with module classes. + if (allow_tag && std::is_empty<T>::value) { + return 0; + } else { + return sizeof(T); + } + } + + static size_t alignment(bool allow_tag) { + if (size(allow_tag) == 0) { + return 0; + } else { + return alignof(T); + } + } +}; + +template <typename T> +class cpp_type_impl { +public: + // Initialize component identifier + static void init(world_t* world, entity_t entity, bool allow_tag = true) { + // If an identifier was already set, check for consistency + if (s_id) { + // If an identifier was registered, a name should've been registered + // as well. + ecs_assert(s_name.c_str() != nullptr, ECS_INTERNAL_ERROR, NULL); + + // A component cannot be registered using a different identifier. + ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, + _::name_helper<T>::name()); + + ecs_assert(allow_tag == s_allow_tag, ECS_INTERNAL_ERROR, 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. + char *path = ecs_get_fullpath(world, entity); + s_id = entity; + s_name = flecs::string(path); + s_allow_tag = allow_tag; + } + + // 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. + static const char* strip_module(world_t *world) { + const char *name = _::name_helper<T>::name(); + entity_t scope = ecs_get_scope(world); + if (!scope) { + return name; + } + + char *path = ecs_get_path_w_sep(world, 0, scope, "::", nullptr); + if (path) { + const char *ptr = strrchr(name, ':'); + ecs_assert(ptr != name, ECS_INTERNAL_ERROR, NULL); + if (ptr) { + ptr --; + ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); + ecs_size_t name_path_len = static_cast<ecs_size_t>(ptr - name); + if (name_path_len <= ecs_os_strlen(path)) { + if (!ecs_os_strncmp(name, path, name_path_len)) { + name = &name[name_path_len + 2]; + } + } + } + } + ecs_os_free(path); + + return name; + } + + // 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) + { + if (!s_id) { + // If no world was provided the component cannot be registered + ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); + s_allow_tag = allow_tag; + } else { + ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); + ecs_assert(s_allow_tag == allow_tag, ECS_INVALID_PARAMETER, 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))) { + if (!s_id) { + s_id = id; + } + + // One type can only be associated with a single type + ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); + + char *symbol = nullptr; + + // 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) { + symbol = symbol_helper<T>::symbol(); + if (!name) { + // If no name was provided, retrieve the name implicitly from + // the name_helper class. + name = strip_module(world); + } + } else { + // If an explicit id is provided but it has no name, inherit + // the name from the type. + if (!ecs_get_name(world, id)) { + name = strip_module(world); + } + } + + ecs_component_desc_t desc = {}; + desc.entity.entity = s_id; + desc.entity.name = name; + desc.entity.sep = "::"; + desc.entity.root_sep = "::"; + desc.entity.symbol = symbol; + desc.size = cpp_type_size<T>::size(allow_tag); + desc.alignment = cpp_type_size<T>::alignment(allow_tag); + + ecs_entity_t entity = ecs_component_init(world, &desc); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(symbol); + + init(world, s_id, allow_tag); + s_id = entity; + } + + // 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. + if (!s_id || (world && !ecs_exists(world, s_id))) { + ecs_entity_t prev_scope = 0; + ecs_id_t prev_with = 0; + + if (world) { + prev_scope = ecs_set_scope(world, 0); + prev_with = ecs_set_with(world, 0); + } + + // This will register a component id, but will not register + // lifecycle callbacks. + id_explicit(world, name, allow_tag); + + // Register lifecycle callbacks, but only if the component has a + // size. Components that don't have a size are tags, and tags don't + // require construction/destruction/copy/move's. */ + if (size()) { + register_lifecycle_actions<T>(world, s_id); + } + + if (prev_with) { + ecs_set_with(world, prev_with); + } + if (prev_scope) { + ecs_set_scope(world, prev_scope); + } + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Obtain a component name + static const char* name(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // If the id is set, the name should also have been set + return s_name.c_str(); + } + + // Obtain a component name, don't register lifecycle if the component hadn't + // been registered yet. While functionally the same could be achieved by + // first calling id_explicit() and then name(), this function ensures + // that the lifecycle callback templates are not instantiated. This allows + // some types (such as module classes) to be created without a default + // constructor. + static const char* name_no_lifecycle(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id_explicit(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // Return + return s_name.c_str(); + } + + // Return the type of a component. + // The type is a vector of component ids. This will return a type with just + // the current component id. + static type_t type(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // Create a type from the component id. + if (!s_type) { + s_type = ecs_type_from_id(world, s_id); + } + + ecs_assert(s_type != nullptr, ECS_INTERNAL_ERROR, NULL); + + return s_type; + } + + // Return the size of a component. + static size_t size() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return cpp_type_size<T>::size(s_allow_tag); + } + + // Return the alignment of a component. + static size_t alignment() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return cpp_type_size<T>::alignment(s_allow_tag); + } + + // Was the component already registered. + static bool registered() { + return s_id != 0; + } + + // 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_type = NULL; + s_name.clear(); + } + +private: + static entity_t s_id; + static type_t s_type; + static flecs::string s_name; + static flecs::string s_symbol; + static bool s_allow_tag; +}; + +// Global templated variables that hold component identifier and other info +template <typename T> entity_t cpp_type_impl<T>::s_id( 0 ); +template <typename T> type_t cpp_type_impl<T>::s_type( nullptr ); +template <typename T> flecs::string cpp_type_impl<T>::s_name; +template <typename T> bool cpp_type_impl<T>::s_allow_tag( true ); + +// Front facing class for implicitly registering a component & obtaining +// static component data + +// Regular type +template <typename T> +class cpp_type<T, if_not_t< is_pair<T>::value >> + : public cpp_type_impl<base_type_t<T>> { }; + +// Pair type +template <typename T> +class cpp_type<T, if_t< is_pair<T>::value >> +{ +public: + // Override id method to return id of pair + static id_t id(world_t *world = nullptr) { + return ecs_pair( + cpp_type< pair_relation_t<T> >::id(world), + cpp_type< pair_object_t<T> >::id(world)); + } +}; + +} // namespace _ + +//////////////////////////////////////////////////////////////////////////////// +//// Register a component with flecs +//////////////////////////////////////////////////////////////////////////////// + +/** Plain old datatype, no lifecycle actions are registered */ +template <typename T> +flecs::entity pod_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 = _::name_helper<T>::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 (_::cpp_type<T>::registered()) { + /* Obtain component id. Because the component is already registered, + * this operation does nothing besides returning the existing id */ + id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id); + + /* If entity has a name check if it matches */ + if (ecs_get_name(world, id) != nullptr) { + if (!implicit_name && id >= EcsFirstUserComponentId) { + char *path = ecs_get_path_w_sep( + world, 0, id, "::", nullptr); + ecs_assert(!strcmp(path, n), + ECS_INCONSISTENT_NAME, name); + ecs_os_free(path); + } + } else { + /* 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, n, "::", "::"); + } + + /* 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_component_desc_t desc = {}; + desc.entity.entity = id; + desc.size = _::cpp_type<T>::size(); + desc.alignment = _::cpp_type<T>::alignment(); + ecs_entity_t entity = ecs_component_init(world, &desc); + (void)entity; + + ecs_assert(entity == id, ECS_INTERNAL_ERROR, NULL); + + /* This functionality could have been put in id_explicit, but since + * this code happens when a component is registered, and the entire API + * calls id_explicit, this would add a lot of overhead to each call. + * This is why when using multiple worlds, components should be + * registered explicitly. */ + } else { + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name. Ensure component is + * looked up from root. */ + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + ecs_entity_t entity; + if (id) { + entity = id; + } else { + entity = ecs_lookup_path_w_sep(world, 0, n, "::", "::", false); + } + + 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 (entity) { + if (!id) { + const char *sym = ecs_get_symbol(world, entity); + ecs_assert(sym != NULL, ECS_INTERNAL_ERROR, NULL); + (void)sym; + + char *symbol = _::symbol_helper<T>::symbol(); + ecs_assert(!ecs_os_strcmp(sym, symbol), ECS_NAME_IN_USE, n); + ecs_os_free(symbol); + + /* If an existing id was provided, it's possible that this id was + * registered with another type. Make sure that in this case at + * least the component size/alignment matches. + * This allows applications to alias two different types to the same + * id, which enables things like redefining a C type in C++ by + * inheriting from it & adding utility functions etc. */ + } else { + const EcsComponent *comp = ecs_get(world, entity, EcsComponent); + if (comp) { + ecs_assert(comp->size == ECS_SIZEOF(T), + ECS_INVALID_COMPONENT_SIZE, NULL); + ecs_assert(comp->alignment == ECS_ALIGNOF(T), + ECS_INVALID_COMPONENT_ALIGNMENT, NULL); + } else { + /* If the existing id is not a component, no checking is + * needed. */ + } + } + + /* If no entity is found, lookup symbol to check if the component was + * registered under a different name. */ + } else { + char *symbol = _::symbol_helper<T>::symbol(); + entity = ecs_lookup_symbol(world, symbol, false); + ecs_assert(entity == 0, ECS_INCONSISTENT_COMPONENT_ID, symbol); + ecs_os_free(symbol); + } + + /* Register id as usual */ + id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id); + } + + return flecs::entity(world, id); +} + +/** Register component */ +template <typename T> +flecs::entity component(flecs::world_t *world, const char *name = nullptr) { + flecs::entity result = pod_component<T>(world, name); + + if (_::cpp_type<T>::size()) { + _::register_lifecycle_actions<T>(world, result); + } + + return result; +} + +/* Register component with existing entity id */ +template <typename T> +void component_for_id(flecs::world_t *world, flecs::id_t id) { + flecs::entity result = pod_component<T>(world, nullptr, true, id); + + ecs_assert(result.id() == id, ECS_INTERNAL_ERROR, NULL); + + if (_::cpp_type<T>::size()) { + _::register_lifecycle_actions<T>(world, result); + } +} + +ECS_DEPRECATED("API detects automatically whether type is trivial") +template <typename T> +flecs::entity relocatable_component(const flecs::world& world, const char *name = nullptr) { + flecs::entity result = pod_component<T>(world, name); + + _::register_lifecycle_actions<T>(world.c_ptr(), result.id()); + + return result; +} + +template <typename T> +flecs::entity_t type_id() { + return _::cpp_type<T>::id(); +} + +} // namespace flecs + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system each +//////////////////////////////////////////////////////////////////////////////// + +namespace flecs +{ + +namespace _ +{ + +// Utility to convert template argument pack to array of term ptrs +struct term_ptr { + void *ptr; + bool is_ref; +}; + +template <typename ... Components> +class term_ptrs { +public: + 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<Components>> + *>(nullptr)...); + } + + array m_terms; + +private: + /* Populate terms array without checking for references */ + bool populate(const ecs_iter_t*, size_t) { return false; } + + template <typename T, typename... Targs> + bool populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { + m_terms[index].ptr = iter->ptrs[index]; + bool is_ref = iter->subjects && iter->subjects[index] != 0; + m_terms[index].is_ref = is_ref; + is_ref |= populate(iter, index + 1, comps ...); + return is_ref; + } +}; + +class invoker { }; + +// Template that figures out from the template parameters of a query/system +// how to pass the value to the each callback +template <typename T, typename = int> +struct each_column { }; + +// Base class +struct each_column_base { + each_column_base(const _::term_ptr& term, size_t row) + : m_term(term), m_row(row) { } + +protected: + const _::term_ptr& m_term; + size_t m_row; +}; + +// If type is not a pointer, return a reference to the type (default case) +template <typename T> +struct each_column<T, if_t< !is_pointer<T>::value && is_actual<T>::value > > + : public each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T& get_row() { + return static_cast<T*>(this->m_term.ptr)[this->m_row]; + } +}; + +// If argument type is not the same as actual component type, return by value. +// This requires that the actual type can be converted to the type. +// A typical scenario where this happens is when using flecs::pair types. +template <typename T> +struct each_column<T, if_t< !is_pointer<T>::value && !is_actual<T>::value> > + : public each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + return static_cast<actual_type_t<T>*>(this->m_term.ptr)[this->m_row]; + } +}; + +// If type is a pointer (indicating an optional value) return the type as is +template <typename T> +struct each_column<T, if_t< is_pointer<T>::value > > + : public each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + if (this->m_term.ptr) { + return &static_cast<actual_type_t<T>>(this->m_term.ptr)[this->m_row]; + } else { + // optional argument doesn't hava a value + return nullptr; + } + } +}; + +// If the query contains component references to other entities, check if the +// current argument is one. +template <typename T, typename = int> +struct each_ref_column : public each_column<T> { + each_ref_column(const _::term_ptr& term, size_t row) + : each_column<T>(term, row) { + + if (term.is_ref) { + // If this is a reference, set the row to 0 as a ref always is a + // single value, not an array. This prevents the application from + // having to do an if-check on whether the column is owned. + // + // This check only happens when the current table being iterated + // over caused the query to match a reference. The check is + // performed once per iterated table. + this->m_row = 0; + } + } +}; + +template <typename Func, typename ... Components> +class each_invoker : public invoker { +public: + // If the number of arguments in the function signature is one more than the + // number of components in the query, an extra entity arg is required. + static constexpr bool PassEntity = + sizeof...(Components) == (arity<Func>::value - 1); + + static_assert(arity<Func>::value > 0, + "each() must have at least one argument"); + + using Terms = typename term_ptrs<Components ...>::array; + + explicit each_invoker(Func&& func) noexcept + : m_func(std::move(func)) { } + + explicit each_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs<Components...> terms; + + if (terms.populate(iter)) { + invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + } else { + invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + } + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast<const each_invoker*>(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template <template<typename X, typename = int> class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { +#ifndef NDEBUG + ecs_table_t *table = iter->table; + if (table) { + ecs_table_lock(iter->world, table); + } +#endif + + flecs::iter it(iter); + for (auto row : it) { + func(it.entity(row), + (ColumnType< remove_reference_t<Components> >(comps, row) + .get_row())...); + } + +#ifndef NDEBUG + if (table) { + ecs_table_unlock(iter->world, table); + } +#endif + } + + // Number of function arguments is equal to number of components, no entity + template <template<typename X, typename = int> class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && !PassEntity> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + flecs::iter it(iter); + for (auto row : it) { + func( (ColumnType< remove_reference_t<Components> >(comps, row) + .get_row())...); + } + } + + template <template<typename X, typename = int> class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + invoke_callback<ColumnType>( + iter, func, index + 1, columns, comps..., columns[index]); + } + + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system iterate action +//////////////////////////////////////////////////////////////////////////////// + +template <typename Func, typename ... Components> +class iter_invoker : public invoker { + static constexpr bool IterOnly = arity<Func>::value == 1; + + using Terms = typename term_ptrs<Components ...>::array; + +public: + explicit iter_invoker(Func&& func) noexcept + : m_func(std::move(func)) { } + + explicit iter_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs<Components...> terms; + terms.populate(iter); + invoke_callback(iter, m_func, 0, terms.m_terms); + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast<const iter_invoker*>(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + +private: + template <typename... Args, if_t<!sizeof...(Args) && IterOnly> = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t, Terms&, Args...) + { + flecs::iter it(iter); + func(it); + } + + template <typename... Targs, if_t<!IterOnly && + (sizeof...(Targs) == sizeof...(Components))> = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, size_t, + Terms&, Targs... comps) + { + flecs::iter it(iter); + +#ifndef NDEBUG + ecs_table_t *table = iter->table; + if (table) { + ecs_table_lock(iter->world, table); + } +#endif + + func(it, ( static_cast< + remove_reference_t< + remove_pointer_t< + actual_type_t<Components> > >* > + (comps.ptr))...); + +#ifndef NDEBUG + if (table) { + ecs_table_unlock(iter->world, table); + } +#endif + } + + template <typename... Targs, if_t<!IterOnly && + (sizeof...(Targs) != sizeof...(Components)) > = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Targs... comps) + { + invoke_callback(iter, func, index + 1, columns, comps..., + columns[index]); + } + + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system action (deprecated) +//////////////////////////////////////////////////////////////////////////////// + +template <typename Func, typename ... Components> +class action_invoker : public invoker { + using Terms = typename term_ptrs<Components ...>::array; +public: + explicit action_invoker(Func&& func) noexcept : m_func(std::move(func)) { } + explicit action_invoker(const Func& func) noexcept : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs<Components...> terms; + terms.populate(iter); + invoke_callback(iter, m_func, 0, terms.m_terms); + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast<const action_invoker*>(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + +private: + template <typename... Targs, + if_t< sizeof...(Targs) == sizeof...(Components) > = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Targs... comps) + { + flecs::iter iter_wrapper(iter); + func(iter_wrapper, (column< + remove_reference_t< remove_pointer_t<Components> > >( + static_cast< remove_reference_t< + remove_pointer_t<Components> > *> + (comps.ptr), iter->count, comps.is_ref))...); + } + + template <typename... Targs, + if_t<sizeof...(Targs) != sizeof...(Components)> = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Targs... comps) + { + invoke_callback(iter, func, index + 1, columns, comps..., + columns[index]); + } + + Func m_func; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility to invoke callback on entity if it has components in signature +//////////////////////////////////////////////////////////////////////////////// + +template<typename ... Args> +class entity_with_invoker_impl; + +template<typename ... Args> +class entity_with_invoker_impl<arg_list<Args ...>> { +public: + using ColumnArray = flecs::array<int32_t, sizeof...(Args)>; + using ConstPtrArray = flecs::array<const void*, sizeof...(Args)>; + using PtrArray = flecs::array<void*, sizeof...(Args)>; + using DummyArray = flecs::array<int, sizeof...(Args)>; + using IdArray = flecs::array<id_t, sizeof...(Args)>; + + template <typename ArrayType> + static bool get_ptrs(world& w, ecs_record_t *r, ecs_table_t *table, + ArrayType& ptrs) + { + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t type = ecs_table_get_type(table); + if (!type) { + return false; + } + + /* Get column indices for components */ + ColumnArray columns ({ + ecs_type_index_of(type, 0, w.id<Args>())... + }); + + /* Get pointers for columns for entity */ + size_t i = 0; + for (int32_t column : columns) { + if (column == -1) { + return false; + } + + ptrs[i ++] = ecs_record_get_column(r, column, 0); + } + + return true; + } + + template <typename ArrayType> + static bool get_mut_ptrs(world& w, ecs_entity_t e, ArrayType& ptrs) { + world_t *world = w.c_ptr(); + + /* Get pointers w/get_mut */ + size_t i = 0; + DummyArray dummy ({ + (ptrs[i ++] = ecs_get_mut_id(world, e, w.id<Args>(), NULL), 0)... + }); + + return true; + } + + template <typename Func> + static bool invoke_get(world_t *world, entity_t id, const Func& func) { + flecs::world w(world); + + ecs_record_t *r = ecs_record_find(world, id); + if (!r) { + return false; + } + + ecs_table_t *table = r->table; + if (!table) { + return false; + } + + ConstPtrArray ptrs; + if (!get_ptrs(w, r, table, ptrs)) { + return false; + } + + invoke_callback(func, 0, ptrs); + + return true; + } + + // 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 <typename Func> + static bool invoke_get_mut(world_t *world, entity_t id, const Func& func) { + flecs::world w(world); + + PtrArray ptrs; + + // 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. + + // Find table for entity + ecs_record_t *r = ecs_record_find(world, id); + ecs_table_t *table = NULL; + 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<Args>()), + elem = store_added(added, elem, prev, next, w.id<Args>()), + prev = next, 0 + )... }); + (void)dummy_before; + + // If table is different, move entity straight to it + if (table != next) { + ecs_ids_t ids; + ids.array = added.ptr(); + ids.count = static_cast<ecs_size_t>(elem); + ecs_commit(world, id, r, next, &ids, NULL); + table = next; + } + + if (!get_ptrs(w, r, table, ptrs)) { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + // When deferred, obtain pointers with regular get_mut + } else { + get_mut_ptrs(w, id, ptrs); + } + + invoke_callback(func, 0, ptrs); + + // Call modified on each component + DummyArray dummy_after ({ + ( ecs_modified_id(world, id, w.id<Args>()), 0)... + }); + (void)dummy_after; + + return true; + } + +private: + template <typename Func, typename ArrayType, typename ... TArgs, + if_t<sizeof...(TArgs) == sizeof...(Args)> = 0> + static void invoke_callback( + const Func& f, size_t, ArrayType&, TArgs&& ... comps) + { + f(*static_cast<typename base_arg_type<Args>::type*>(comps)...); + } + + template <typename Func, typename ArrayType, typename ... TArgs, + if_t<sizeof...(TArgs) != sizeof...(Args)> = 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 <typename Func, typename U = int> +class entity_with_invoker { + static_assert(function_traits<Func>::value, "type is not callable"); +}; + +template <typename Func> +class entity_with_invoker<Func, if_t< is_callable<Func>::value > > + : public entity_with_invoker_impl< arg_list_t<Func> > +{ + static_assert(function_traits<Func>::arity > 0, + "function must have at least one argument"); +}; + +} // namespace _ + +} // namespace flecs + +namespace flecs { + +template<typename Base> +class term_id_builder_i { +public: + term_id_builder_i() : m_term_id(nullptr) { } + + virtual ~term_id_builder_i() { } + + template<typename T> + Base& entity() { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + m_term_id->entity = _::cpp_type<T>::id(world()); + return *this; + } + + Base& entity(flecs::id_t id) { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + m_term_id->entity = id; + return *this; + } + + Base& name(const char *name) { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + // Const cast is safe, when the value is actually used to construct a + // query, it will be duplicated. + m_term_id->name = const_cast<char*>(name); + return *this; + } + + Base& var(flecs::var_kind_t var = flecs::VarIsVariable) { + m_term_id->var = static_cast<ecs_var_kind_t>(var); + return *this; + } + + Base& var(const char *name) { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + // Const cast is safe, when the value is actually used to construct a + // query, it will be duplicated. + m_term_id->name = const_cast<char*>(name); + return var(); // Default to VarIsVariable + } + + Base& set(uint8_t mask, const flecs::id_t relation = flecs::IsA) + { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + m_term_id->set.mask = mask; + m_term_id->set.relation = relation; + return *this; + } + + Base& superset(const flecs::id_t relation = flecs::IsA, uint8_t mask = 0) + { + ecs_assert(!(mask & flecs::SubSet), ECS_INVALID_PARAMETER, NULL); + return set(flecs::SuperSet | mask, relation); + } + + Base& subset(const flecs::id_t relation = flecs::IsA, uint8_t mask = 0) + { + ecs_assert(!(mask & flecs::SuperSet), ECS_INVALID_PARAMETER, NULL); + return set(flecs::SubSet | mask, relation); + } + + Base& min_depth(int32_t min_depth) { + m_term_id->set.min_depth = min_depth; + return *this; + } + + Base& max_depth(int32_t max_depth) { + m_term_id->set.max_depth = max_depth; + return *this; + } + + ecs_term_id_t *m_term_id; + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } +}; + +template<typename Base> +class term_builder_i : public term_id_builder_i<Base> { +public: + term_builder_i() : m_term(nullptr) { } + + term_builder_i(ecs_term_t *term_ptr) { + set_term(term_ptr); + } + + template<typename T> + Base& id() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = _::cpp_type<T>::id(world()); + return *this; + } + + template<typename R, typename O> + Base& id() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = _::cpp_type<R>::id(world()); + m_term->args[1].entity = _::cpp_type<O>::id(world()); + return *this; + } + + template<typename R> + Base& id(id_t o) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = _::cpp_type<R>::id(world()); + m_term->args[1].entity = o; + return *this; + } + + Base& id(id_t id) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = id; + return *this; + } + + Base& id(const flecs::type& type); + + Base& id(id_t r, id_t o) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = r; + m_term->args[1].entity = o; + return *this; + } + + Base& expr(const char *expr) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + const char *ptr; + if ((ptr = ecs_parse_term(world(), nullptr, expr, expr, m_term)) == nullptr) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + // Should not have more than one term + ecs_assert(ptr[0] == 0, ECS_INVALID_PARAMETER, NULL); + return *this; + } + + Base& predicate() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + this->m_term_id = &m_term->pred; + return *this; + } + + Base& subject() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + this->m_term_id = &m_term->args[0]; + return *this; + } + + Base& object() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + this->m_term_id = &m_term->args[1]; + return *this; + } + + Base& subject(entity_t entity) { + this->subject(); + this->m_term_id->entity = entity; + return *this; + } + + Base& object(entity_t entity) { + this->object(); + this->m_term_id->entity = entity; + return *this; + } + + template<typename T> + Base& subject() { + this->subject(); + this->m_term_id->entity = _::cpp_type<T>::id(world()); + return *this; + } + + template<typename T> + Base& object() { + this->object(); + this->m_term_id->entity = _::cpp_type<T>::id(world()); + return *this; + } + + Base& role(id_t role) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->role = role; + return *this; + } + + Base& inout(flecs::inout_kind_t inout) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->inout = static_cast<ecs_inout_kind_t>(inout); + return *this; + } + + Base& oper(flecs::oper_kind_t oper) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->oper = static_cast<ecs_oper_kind_t>(oper); + return *this; + } + + Base& singleton() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_term->id || m_term->pred.entity, ECS_INVALID_PARAMETER, NULL); + + flecs::id_t pred = m_term->id; + if (!pred) { + pred = m_term->pred.entity; + } + + ecs_assert(pred != 0, ECS_INVALID_PARAMETER, NULL); + + m_term->args[0].entity = pred; + + return *this; + } + + flecs::id id() { + return flecs::id(world(), m_term->id); + } + + flecs::entity get_subject() { + return flecs::entity(world(), m_term->args[0].entity); + } + + flecs::entity get_object() { + return flecs::entity(world(), m_term->args[1].entity); + } + + flecs::inout_kind_t inout() { + return static_cast<flecs::inout_kind_t>(m_term->inout); + } + + flecs::oper_kind_t oper() { + return static_cast<flecs::oper_kind_t>(m_term->oper); + } + + ecs_term_t *m_term; + +protected: + virtual flecs::world_t* world() = 0; + + void set_term(ecs_term_t *term) { + m_term = term; + if (term) { + this->m_term_id = &m_term->args[0]; // default to subject + } else { + this->m_term_id = nullptr; + } + } + +private: + operator Base&() { + return *static_cast<Base*>(this); + } +}; + +// Class that describes a term +class term final : public term_builder_i<term> { +public: + term(flecs::world_t *world_ptr) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { value.move = true; } + + term(flecs::world_t *world_ptr, id_t id) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { + value.move = true; + this->id(id); + } + + term(flecs::world_t *world_ptr, ecs_term_t t) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { + value = t; + value.move = false; + this->set_term(&value); + } + + term(flecs::world_t *world_ptr, id_t r, id_t o) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { + value.move = true; + this->id(r, o); + } + + term(const term& obj) : term_builder_i<term>(&value) { + m_world = obj.m_world; + value = ecs_term_copy(&obj.value); + this->set_term(&value); + } + + term(term&& obj) : term_builder_i<term>(&value) { + m_world = obj.m_world; + value = ecs_term_move(&obj.value); + obj.reset(); + this->set_term(&value); + } + + term& operator=(const term& obj) { + ecs_assert(m_world == obj.m_world, ECS_INVALID_PARAMETER, NULL); + ecs_term_fini(&value); + value = ecs_term_copy(&obj.value); + this->set_term(&value); + return *this; + } + + term& operator=(term&& obj) { + ecs_assert(m_world == obj.m_world, ECS_INVALID_PARAMETER, NULL); + ecs_term_fini(&value); + value = obj.value; + this->set_term(&value); + obj.reset(); + return *this; + } + + ~term() { + ecs_term_fini(&value); + } + + void reset() { + value = {}; + this->set_term(nullptr); + } + + int finalize() { + return ecs_term_finalize(m_world, nullptr, nullptr, &value); + } + + bool is_set() { + return ecs_term_is_initialized(&value); + } + + bool is_trivial() { + return ecs_term_is_trivial(&value); + } + + ecs_term_t move() { /* explicit move to ecs_term_t */ + return ecs_term_move(&value); + } + + ecs_term_t value; + +protected: + flecs::world_t* world() override { return m_world; } + +private: + flecs::world_t *m_world; +}; + +// Filter builder interface +template<typename Base, typename ... Components> +class filter_builder_i : public term_builder_i<Base> { +public: + filter_builder_i(ecs_filter_desc_t *desc, int32_t term_index = 0) + : m_term_index(term_index) + , m_desc(desc) { } + + Base& expr(const char *expr) { + m_desc->expr = expr; + return *this; + } + + Base& substitute_default(bool value = true) { + m_desc->substitute_default = value; + return *this; + } + + Base& term() { + if (m_term_index >= ECS_TERM_DESC_CACHE_SIZE) { + if (m_term_index == ECS_TERM_DESC_CACHE_SIZE) { + m_desc->terms_buffer = ecs_os_calloc_n( + ecs_term_t, m_term_index + 1); + ecs_os_memcpy_n(m_desc->terms_buffer, m_desc->terms, + ecs_term_t, m_term_index); + ecs_os_memset_n(m_desc->terms, 0, + ecs_term_t, ECS_TERM_DESC_CACHE_SIZE); + } else { + m_desc->terms_buffer = ecs_os_realloc_n(m_desc->terms_buffer, + ecs_term_t, m_term_index + 1); + } + + m_desc->terms_buffer_count = m_term_index + 1; + + this->set_term(&m_desc->terms_buffer[m_term_index]); + } else { + this->set_term(&m_desc->terms[m_term_index]); + } + + m_term_index ++; + return *this; + } + + Base& arg(int32_t term_index) { + ecs_assert(term_index > 0, ECS_INVALID_PARAMETER, NULL); + int32_t prev_index = m_term_index; + m_term_index = term_index - 1; + this->term(); + m_term_index = prev_index; + ecs_assert(ecs_term_is_initialized(this->m_term), ECS_INVALID_PARAMETER, NULL); + return *this; + } + + template<typename T> + Base& term() { + this->term(); + *this->m_term = flecs::term(world()).id<T>().move(); + return *this; + } + + Base& term(id_t id) { + this->term(); + *this->m_term = flecs::term(world()).id(id).move(); + return *this; + } + + template<typename R, typename O> + Base& term() { + this->term(); + *this->m_term = flecs::term(world()).id<R, O>().move(); + return *this; + } + + template<typename R> + Base& term(id_t o) { + this->term(); + *this->m_term = flecs::term(world()).id<R>(o).move(); + return *this; + } + + Base& term(id_t r, id_t o) { + this->term(); + *this->m_term = flecs::term(world()).id(r, o).move(); + return *this; + } + + Base& term(const flecs::type& type) { + this->term(); + *this->m_term = flecs::term(world()).id(type).move(); + return *this; + } + + Base& term(const char *expr) { + this->term(); + *this->m_term = flecs::term(world()).expr(expr).move(); + return *this; + } + + Base& term(flecs::term& term) { + this->term(); + *this->m_term = term.move(); + return *this; + } + + Base& term(flecs::term&& term) { + this->term(); + *this->m_term = term.move(); + return *this; + } + + void populate_filter_from_pack() { + flecs::array<flecs::id_t, sizeof...(Components)> ids ({ + (_::cpp_type<Components>::id(world()))... + }); + + flecs::array<flecs::inout_kind_t, sizeof...(Components)> inout_kinds ({ + (type_to_inout<Components>())... + }); + + flecs::array<flecs::oper_kind_t, sizeof...(Components)> oper_kinds ({ + (type_to_oper<Components>())... + }); + + size_t i = 0; + for (auto id : ids) { + this->term(id).inout(inout_kinds[i]).oper(oper_kinds[i]); + i ++; + } + } + +protected: + virtual flecs::world_t* world() = 0; + int32_t m_term_index; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + template <typename T, if_t< is_const<T>::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() const { + return flecs::In; + } + + template <typename T, if_t< is_reference<T>::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() const { + return flecs::Out; + } + + template <typename T, if_not_t< + is_const<T>::value || is_reference<T>::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() const { + return flecs::InOutDefault; + } + + template <typename T, if_t< is_pointer<T>::value > = 0> + constexpr flecs::oper_kind_t type_to_oper() const { + return flecs::Optional; + } + + template <typename T, if_not_t< is_pointer<T>::value > = 0> + constexpr flecs::oper_kind_t type_to_oper() const { + return flecs::And; + } + + ecs_filter_desc_t *m_desc; +}; + +// Query builder interface +template<typename Base, typename ... Components> +class query_builder_i : public filter_builder_i<Base, Components ...> { + using BaseClass = filter_builder_i<Base, Components ...>; +public: + query_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) { } + + query_builder_i(ecs_query_desc_t *desc, int32_t term_index = 0) + : BaseClass(&desc->filter, term_index) + , m_desc(desc) { } + + /** 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 (sub)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * 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 <typename T> + Base& order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_order_by_action_t cmp = reinterpret_cast<ecs_order_by_action_t>(compare); + return this->order_by(_::cpp_type<T>::id(world()), cmp); + } + + /** Sort the output of a query. + * Same as order_by<T>, 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*)) { + m_desc->order_by = reinterpret_cast<ecs_order_by_action_t>(compare); + m_desc->order_by_component = component; + return *this; + } + + /** Group and sort matched tables. + * Similar yo ecs_query_order_by, but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * 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 rank The rank action. + */ + template <typename T> + Base& group_by(int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + ecs_group_by_action_t rnk = reinterpret_cast<ecs_group_by_action_t>(rank); + return this->group_by(_::cpp_type<T>::id(this->m_world), rnk); + } + + /** Group and sort matched tables. + * Same as group_by<T>, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param rank The rank action. + */ + Base& group_by(flecs::entity_t component, int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + m_desc->group_by = reinterpret_cast<ecs_group_by_action_t>(rank); + m_desc->group_by_id = component; + return *this; + } + + /** Specify parent query (creates subquery) */ + Base& parent(const query_base& parent); + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + ecs_query_desc_t *m_desc; +}; + +// System builder interface +template<typename Base, typename ... Components> +class system_builder_i : public query_builder_i<Base, Components ...> { + using BaseClass = query_builder_i<Base, Components ...>; +public: + system_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) + , m_add_count(0) { } + + system_builder_i(ecs_system_desc_t *desc) + : BaseClass(&desc->query) + , m_desc(desc) + , m_add_count(0) { } + + /** Specify string-based signature. */ + Base& signature(const char *signature) { + m_desc->query.filter.expr = signature; + return *this; + } + + /** Specify when the system should be ran. + * Use this function to set in which phase the system should run or whether + * the system is reactive. Valid values for reactive systems are: + * + * flecs::OnAdd + * flecs::OnRemove + * flecs::OnSet + * flecs::UnSet + * + * @param kind The kind that specifies when the system should be ran. + */ + Base& kind(entity_t kind) { + m_desc->entity.add[0] = kind; + 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(FLECS_FLOAT interval) { + m_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) { + m_desc->rate = rate; + m_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) { + m_desc->rate = rate; + return *this; + } + + /** System is an on demand system */ + Base& on_demand() { + m_desc->entity.add[m_add_count ++] = flecs::OnDemand; + return *this; + } + + /** System is a hidden system */ + Base& hidden() { + m_desc->entity.add[m_add_count ++] = flecs::Hidden; + return *this; + } + + /** Associate system with entity */ + Base& self(flecs::entity self) { + m_desc->self = self; + return *this; + } + + /** Set system context */ + Base& ctx(void *ptr) { + m_desc->ctx = ptr; + return *this; + } + + ECS_DEPRECATED("use interval") + Base& period(FLECS_FLOAT period) { + return this->interval(period); + } + + ECS_DEPRECATED("use ctx") + Base& set_context(void *ptr) { + ctx(ptr); + return *this; + } + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + ecs_system_desc_t *m_desc; + int32_t m_add_count; +}; + +// Observer builder interface +template<typename Base, typename ... Components> +class observer_builder_i : public filter_builder_i<Base, Components ...> { + using BaseClass = filter_builder_i<Base, Components ...>; +public: + observer_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) + , m_event_count(0) { } + + observer_builder_i(ecs_observer_desc_t *desc) + : BaseClass(&desc->filter) + , m_desc(desc) + , m_event_count(0) { } + + /** Specify when the system should be ran. + * Use this function to set in which phase the system should run or whether + * the system is reactive. Valid values for reactive systems are: + * + * flecs::OnAdd + * flecs::OnRemove + * flecs::OnSet + * flecs::UnSet + * + * @param kind The kind that specifies when the system should be ran. + */ + Base& event(entity_t kind) { + m_desc->events[m_event_count ++] = kind; + return *this; + } + + /** Associate observer with entity */ + Base& self(flecs::entity self) { + m_desc->self = self; + return *this; + } + + /** Set system context */ + Base& ctx(void *ptr) { + m_desc->ctx = ptr; + return *this; + } + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + ecs_observer_desc_t *m_desc; + int32_t m_event_count; +}; + +// Filter builder +template<typename ... Components> +class filter_builder_base + : public filter_builder_i<filter_builder_base<Components...>, Components ...> +{ +public: + filter_builder_base(flecs::world_t *world) + : filter_builder_i<filter_builder_base<Components...>, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + this->populate_filter_from_pack(); + } + + filter_builder_base(const filter_builder_base& obj) + : filter_builder_i<filter_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + filter_builder_base(filter_builder_base&& obj) + : filter_builder_i<filter_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + operator filter<Components ...>() const; + + operator ecs_filter_t() const { + ecs_filter_t f; + + int res = ecs_filter_init(this->m_world, &f, &this->m_desc); + if (res != 0) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (this->m_desc.terms_buffer) { + ecs_os_free(this->m_desc.terms_buffer); + } + + return f; + } + + filter<Components ...> build() const; + + ecs_filter_desc_t m_desc; + + flecs::world_t* world() override { return m_world; } + +protected: + flecs::world_t *m_world; +}; + +template<typename ... Components> +class filter_builder final : public filter_builder_base<Components...> { +public: + filter_builder(flecs::world_t *world) + : filter_builder_base<Components ...>(world) { } + + operator filter<>() const; +}; + +// Query builder +template<typename ... Components> +class query_builder_base + : public query_builder_i<query_builder_base<Components...>, Components ...> +{ +public: + query_builder_base(flecs::world_t *world) + : query_builder_i<query_builder_base<Components...>, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + this->populate_filter_from_pack(); + } + + query_builder_base(const query_builder_base& obj) + : query_builder_i<query_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + query_builder_base(query_builder_base&& obj) + : query_builder_i<query_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + operator query<Components ...>() const; + + operator ecs_query_t*() const { + ecs_query_t *result = ecs_query_init(this->m_world, &this->m_desc); + + if (!result) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (this->m_desc.filter.terms_buffer) { + ecs_os_free(m_desc.filter.terms_buffer); + } + + return result; + } + + query<Components ...> build() const; + + ecs_query_desc_t m_desc; + + flecs::world_t* world() override { return m_world; } + +protected: + flecs::world_t *m_world; +}; + +template<typename ... Components> +class query_builder final : public query_builder_base<Components...> { +public: + query_builder(flecs::world_t *world) + : query_builder_base<Components ...>(world) { } + + operator query<>() const; +}; + +template<typename ... Components> +class system_builder final + : public system_builder_i<system_builder<Components ...>, Components ...> +{ + using Class = system_builder<Components ...>; +public: + explicit system_builder(flecs::world_t* world, const char *name = nullptr, const char *expr = nullptr) + : system_builder_i<Class, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + m_desc.entity.name = name; + m_desc.entity.sep = "::"; + m_desc.entity.add[0] = flecs::OnUpdate; + m_desc.query.filter.expr = expr; + this->populate_filter_from_pack(); + } + + // put using outside of action so we can still use it without it being + // flagged as deprecated. + template <typename Func> + using action_invoker_t = typename _::iter_invoker< + typename std::decay<Func>::type, Components...>; + + template <typename Func> + ECS_DEPRECATED("use each or iter") + system<Components...> action(Func&& func) const; + + /* Iter (or each) is mandatory and always the last thing that + * is added in the fluent method chain. Create system signature from both + * template parameters and anything provided by the signature method. */ + template <typename Func> + system<Components...> iter(Func&& func) const; + + /* Each is similar to action, but accepts a function that operates on a + * single entity */ + template <typename Func> + system<Components...> each(Func&& func) const; + + ecs_system_desc_t m_desc; + +protected: + flecs::world_t* world() override { return m_world; } + flecs::world_t *m_world; + +private: + template <typename Invoker, typename Func> + entity_t build(Func&& func, bool is_each) const { + auto ctx = FLECS_NEW(Invoker)(std::forward<Func>(func)); + + entity_t e, kind = m_desc.entity.add[0]; + bool is_trigger = kind == flecs::OnAdd || kind == flecs::OnRemove; + + if (is_trigger) { + ecs_trigger_desc_t desc = {}; + ecs_term_t term = m_desc.query.filter.terms[0]; + if (ecs_term_is_initialized(&term)) { + desc.term = term; + } else { + desc.expr = m_desc.query.filter.expr; + } + + desc.entity.entity = m_desc.entity.entity; + desc.events[0] = kind; + desc.callback = Invoker::run; + desc.self = m_desc.self; + desc.ctx = m_desc.ctx; + desc.binding_ctx = ctx; + desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj<Invoker>); + + e = ecs_trigger_init(m_world, &desc); + } else { + ecs_system_desc_t desc = m_desc; + desc.callback = Invoker::run; + desc.self = m_desc.self; + desc.query.filter.substitute_default = is_each; + desc.binding_ctx = ctx; + desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj<Invoker>); + + e = ecs_system_init(m_world, &desc); + } + + if (this->m_desc.query.filter.terms_buffer) { + ecs_os_free(m_desc.query.filter.terms_buffer); + } + + return e; + } +}; + +template<typename ... Components> +class observer_builder final + : public observer_builder_i<observer_builder<Components ...>, Components ...> +{ + using Class = observer_builder<Components ...>; +public: + explicit observer_builder(flecs::world_t* world, const char *name = nullptr, const char *expr = nullptr) + : observer_builder_i<Class, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + m_desc.entity.name = name; + m_desc.entity.sep = "::"; + m_desc.entity.add[0] = flecs::OnUpdate; + m_desc.filter.expr = expr; + this->populate_filter_from_pack(); + } + + /* Iter (or each) is mandatory and always the last thing that + * is added in the fluent method chain. Create system signature from both + * template parameters and anything provided by the signature method. */ + template <typename Func> + observer<Components...> iter(Func&& func) const; + + /* Each is similar to action, but accepts a function that operates on a + * single entity */ + template <typename Func> + observer<Components...> each(Func&& func) const; + + ecs_observer_desc_t m_desc; + +protected: + flecs::world_t* world() override { return m_world; } + flecs::world_t *m_world; + +private: + template <typename Invoker, typename Func> + entity_t build(Func&& func, bool is_each) const { + auto ctx = FLECS_NEW(Invoker)(std::forward<Func>(func)); + + ecs_observer_desc_t desc = m_desc; + desc.callback = Invoker::run; + desc.filter.substitute_default = is_each; + desc.binding_ctx = ctx; + desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj<Invoker>); + + ecs_entity_t result = ecs_observer_init(m_world, &desc); + + if (this->m_desc.filter.terms_buffer) { + ecs_os_free(m_desc.filter.terms_buffer); + } + + return result; + } +}; + +} + +#ifdef FLECS_DEPRECATED + +namespace flecs +{ + +/* Deprecated functions */ +template<typename Base> +class type_deprecated { +public: + + template <typename T, typename C> + ECS_DEPRECATED("use add<Relation, Object>") + type& add_trait() { + static_cast<Base*>(this)->add(ecs_pair( + _::cpp_type<T>::id(world()), + _::cpp_type<C>::id(world()))); + return *base(); + } + + template <typename T> + ECS_DEPRECATED("use add<Relation>(const flecs::entity&)") + type& add_trait(const flecs::entity& c) { + static_cast<Base*>(this)->add(ecs_pair(_::cpp_type<T>::id(world()), c.id())); + return *base(); + } + + ECS_DEPRECATED("use add(const flecs::entity&, const flecs::entity&)") + type& add_trait(const flecs::entity& t, const flecs::entity& c) { + static_cast<Base*>(this)->add(ecs_pair(t.id(), c.id())); + return *base(); + } + + template <typename C> + ECS_DEPRECATED("use add_object<Object>(const flecs::entity&)") + type& add_trait_tag(const flecs::entity& t) { + static_cast<Base*>(this)->add(ecs_pair(t.id(), _::cpp_type<C>::id(world()))); + return *base(); + } + +private: + Base* base() { return static_cast<Base*>(this); } + flecs::world_t* world() { return base()->world().c_ptr(); } +}; + +} +#else +template <typename Base> +class type_deprecated { }; +#endif + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// A collection of component ids used to describe the contents of a table +//////////////////////////////////////////////////////////////////////////////// + +template <typename Base> +class type_base : public type_deprecated<type> { +public: + explicit type_base( + world_t *world, const char *name = nullptr, const char *expr = nullptr) + { + ecs_type_desc_t desc = {}; + desc.entity.name = name; + desc.ids_expr = expr; + m_entity = flecs::entity(world, ecs_type_init(world, &desc)); + sync_from_flecs(); + } + + explicit type_base(world_t *world, type_t t) + : m_entity( world, static_cast<flecs::id_t>(0) ) + , m_type( t ) + , m_normalized( t ) { } + + type_base(type_t t) + : m_type( t ) + , m_normalized( t ) { } + + Base& add(const Base& t) { + m_type = ecs_type_add(world(), m_type, t.id()); + m_normalized = ecs_type_merge(world(), m_normalized, t, nullptr); + sync_from_me(); + return *this; + } + + Base& add(id_t e) { + m_type = ecs_type_add(world(), m_type, e); + m_normalized = ecs_type_add(world(), m_normalized, e); + sync_from_me(); + return *this; + } + + template <typename T> + Base& add() { + return this->add(_::cpp_type<T>::id(world())); + } + + Base& add(entity_t relation, entity_t object) { + return this->add(ecs_pair(relation, object)); + } + + template <typename Relation, typename Object> + Base& add() { + return this->add<Relation>(_::cpp_type<Object>::id(world())); + } + + Base& is_a(entity_t object) { + return this->add(flecs::IsA, object); + } + + Base& child_of(entity_t object) { + return this->add(flecs::ChildOf, object); + } + + template <typename Relation> + Base& add(entity_t object) { + return this->add(_::cpp_type<Relation>::id(world()), object); + } + + template <typename Object> + Base& add_w_object(entity_t relation) { + return this->add(relation, _::cpp_type<Object>::id(world())); + } + + bool has(id_t id) { + return ecs_type_has_id(world(), m_normalized, id, false); + } + + bool has(id_t relation, id_t object) { + return ecs_type_has_id(world(), m_normalized, + ecs_pair(relation, object), false); + } + + template <typename T> + bool has() { + return this->has(_::cpp_type<T>::id(world())); + } + + template <typename Relation, typename Object> + bool has() { + return this->has(_::cpp_type<flecs::pair<Relation, Object>>::id(world())); + } + + template <typename T> + Base& component() { + component_for_id<T>(world(), m_entity); + return *this; + } + + flecs::string str() const { + char *str = ecs_type_str(world(), m_type); + return flecs::string(str); + } + + type_t c_ptr() const { + return m_type; + } + + flecs::id_t id() const { + return m_entity.id(); + } + + flecs::entity entity() const { + return m_entity; + } + + flecs::world world() const { + return m_entity.world(); + } + + type_t c_normalized() const { + return m_normalized; + } + + void enable() const { + ecs_enable(world(), id(), true); + } + + void disable() const { + ecs_enable(world(), id(), false); + } + + flecs::vector<flecs::id_t> vector() { + return flecs::vector<flecs::id_t>( const_cast<ecs_vector_t*>(m_normalized)); + } + + flecs::id get(int32_t index) { + return flecs::id(world(), vector().get(index)); + } + + /* Implicit conversion to type_t */ + operator type_t() const { return m_normalized; } + + operator Base&() { return *static_cast<Base*>(this); } + +private: + void sync_from_me() { + if (!id()) { + return; + } + + EcsType *tc = ecs_get_mut(world(), id(), EcsType, NULL); + ecs_assert(tc != NULL, ECS_INTERNAL_ERROR, NULL); + tc->type = m_type; + tc->normalized = m_normalized; + ecs_modified(world(), id(), EcsType); + + } + + void sync_from_flecs() { + if (!id()) { + return; + } + + EcsType *tc = ecs_get_mut(world(), id(), EcsType, NULL); + ecs_assert(tc != NULL, ECS_INTERNAL_ERROR, NULL); + m_type = tc->type; + m_normalized = tc->normalized; + ecs_modified(world(), id(), EcsType); + } + + flecs::entity m_entity; + type_t m_type; + type_t m_normalized; +}; + +class type : public type_base<type> { +public: + explicit type( + world_t *world, const char *name = nullptr, const char *expr = nullptr) + : type_base(world, name, expr) { } + + explicit type(world_t *world, type_t t) : type_base(world, t) { } + + type(type_t t) : type_base(t) { } +}; + +class pipeline : public type_base<pipeline> { +public: + explicit pipeline(world_t *world, const char *name) : type_base(world, name) + { + this->entity().add(flecs::Pipeline); + } +}; + +} // namespace flecs + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Define a module +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +flecs::entity module(const flecs::world& world, const char *name = nullptr) { + ecs_set_scope(world.c_ptr(), 0); + flecs::entity result = pod_component<T>(world, name, false); + ecs_add_module_tag(world, result.id()); + ecs_set_scope(world.c_ptr(), result.id()); + + // Only register copy/move/dtor, make sure to not instantiate ctor as the + // default ctor doesn't work for modules. Additionally, the module ctor + // should only be invoked once per import. + EcsComponentLifecycle cl{}; + cl.copy = _::copy<T>(); + cl.move = _::move<T>(); + cl.dtor = _::dtor<T>(); + ecs_set_component_actions_w_entity(world, result, &cl); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Import a module +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +ecs_entity_t do_import(world& world, const char *symbol) { + ecs_trace_1("import %s", _::name_helper<T>::name()); + ecs_log_push(); + + ecs_entity_t scope = ecs_get_scope(world); + + // Create custom storage to prevent object destruction + T* module_data = static_cast<T*>(ecs_os_malloc(sizeof(T))); + FLECS_PLACEMENT_NEW(module_data, T(world)); + + ecs_set_scope(world, scope); + + // It should now be possible to lookup the module + ecs_entity_t m = ecs_lookup_symbol(world, symbol, true); + ecs_assert(m != 0, ECS_MODULE_UNDEFINED, symbol); + + _::cpp_type<T>::init(world, m, false); + + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INTERNAL_ERROR, NULL); + + // Set module singleton component + + T* module_ptr = static_cast<T*>( + ecs_get_mut_id(world, m, + _::cpp_type<T>::id_explicit(world, nullptr, false), NULL)); + + *module_ptr = std::move(*module_data); + + // Don't dtor, as a module should only be destructed once when the module + // component is removed. + ecs_os_free(module_data); + + // Add module tag + ecs_add_id(world, m, flecs::Module); + + ecs_log_pop(); + + return m; +} + +template <typename T> +flecs::entity import(world& world) { + char *symbol = _::symbol_helper<T>::symbol(); + + ecs_entity_t m = ecs_lookup_symbol(world.c_ptr(), symbol, true); + + if (!_::cpp_type<T>::registered()) { + + /* Module is registered with world, initialize static data */ + if (m) { + _::cpp_type<T>::init(world.c_ptr(), m, false); + + /* Module is not yet registered, register it now */ + } else { + m = do_import<T>(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<T>(world, symbol); + } + + ecs_os_free(symbol); + + return flecs::entity(world, m); +} + +} // namespace flecs +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Ad hoc queries (filters) +//////////////////////////////////////////////////////////////////////////////// + +class filter_base { +public: + filter_base() + : m_world(nullptr) + , m_filter({}) { } + + filter_base(world_t *world, ecs_filter_t *filter = NULL) + : m_world(world) { + ecs_filter_move(&m_filter, filter); + } + + /** Get pointer to C filter object. + */ + const filter_t* c_ptr() const { + if (m_filter.term_count) { + return &m_filter; + } else { + return NULL; + } + } + + filter_base(const filter_base& obj) { + this->m_world = obj.m_world; + ecs_filter_copy(&m_filter, &obj.m_filter); + } + + filter_base& operator=(const filter_base& obj) { + this->m_world = obj.m_world; + ecs_filter_copy(&m_filter, &obj.m_filter); + return *this; + } + + filter_base(filter_base&& obj) { + this->m_world = obj.m_world; + ecs_filter_move(&m_filter, &obj.m_filter); + } + + filter_base& operator=(filter_base&& obj) { + this->m_world = obj.m_world; + ecs_filter_move(&m_filter, &obj.m_filter); + return *this; + } + + + /** Free the filter. + */ + ~filter_base() { + ecs_filter_fini(&m_filter); + } + + template <typename Func> + void iter(Func&& func) const { + ecs_iter_t it = ecs_filter_iter(m_world, &m_filter); + while (ecs_filter_next(&it)) { + _::iter_invoker<Func>(func).invoke(&it); + } + } + + template <typename Func> + void each_term(const Func& func) { + for (int i = 0; i < m_filter.term_count; i ++) { + flecs::term t(m_world, m_filter.terms[i]); + func(t); + } + } + + flecs::term term(int32_t index) { + return flecs::term(m_world, m_filter.terms[index]); + } + + int32_t term_count() { + return m_filter.term_count; + } + + flecs::string str() { + char *result = ecs_filter_str(m_world, &m_filter); + return flecs::string(result); + } + +protected: + world_t *m_world; + filter_t m_filter; +}; + + +template<typename ... Components> +class filter : public filter_base { + using Terms = typename _::term_ptrs<Components...>::array; + +public: + filter() { } + + filter(world_t *world, filter_t *f) + : filter_base(world, f) { } + + explicit filter(const world& world, const char *expr = nullptr) + : filter_base(world.c_ptr()) + { + auto qb = world.filter_builder<Components ...>() + .expr(expr); + + if (!expr) { + qb.substitute_default(); + } + + flecs::filter_t f = qb; + ecs_filter_move(&m_filter, &f); + } + + filter(const filter& obj) : filter_base(obj) { } + + filter& operator=(const filter& obj) { + *this = obj; + return *this; + } + + filter(filter&& obj) : filter_base(std::move(obj)) { } + + filter& operator=(filter&& obj) { + filter_base(std::move(obj)); + return *this; + } + + template <typename Func> + void each(Func&& func) const { + iterate<_::each_invoker>(std::forward<Func>(func), ecs_filter_next); + } + + template <typename Func> + void iter(Func&& func) const { + iterate<_::iter_invoker>(std::forward<Func>(func), ecs_filter_next); + } + +private: + template < template<typename Func, typename ... Comps> class Invoker, typename Func, typename NextFunc, typename ... Args> + void iterate(Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = ecs_filter_iter(m_world, &m_filter); + while (next(&it, std::forward<Args>(args)...)) { + Invoker<Func, Components...>(std::move(func)).invoke(&it); + } + } +}; + +} + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Snapshots make a copy of the world state that can be restored +//////////////////////////////////////////////////////////////////////////////// + +class snapshot final { +public: + explicit snapshot(const world& world) + : m_world( world ) + , m_snapshot( nullptr ) { } + + snapshot(const snapshot& obj) + : m_world( obj.m_world ) + { + ecs_iter_t it = ecs_snapshot_iter(obj.m_snapshot, nullptr); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + } + + snapshot(snapshot&& obj) + : m_world(obj.m_world) + , m_snapshot(obj.m_snapshot) + { + obj.m_snapshot = nullptr; + } + + snapshot& operator=(const snapshot& obj) { + ecs_assert(m_world.c_ptr() == obj.m_world.c_ptr(), ECS_INVALID_PARAMETER, NULL); + ecs_iter_t it = ecs_snapshot_iter(obj.m_snapshot, nullptr); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + return *this; + } + + snapshot& operator=(snapshot&& obj) { + ecs_assert(m_world.c_ptr() == obj.m_world.c_ptr(), ECS_INVALID_PARAMETER, NULL); + m_snapshot = obj.m_snapshot; + obj.m_snapshot = nullptr; + return *this; + } + + void take() { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + m_snapshot = ecs_snapshot_take(m_world.c_ptr()); + } + + template <typename F> + void take(const F& f) { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + ecs_iter_t it = ecs_filter_iter(m_world, f.c_ptr()); + + printf("filter = %s\n", ecs_filter_str(m_world, f.c_ptr())); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + } + + void restore() { + if (m_snapshot) { + ecs_snapshot_restore(m_world.c_ptr(), m_snapshot); + m_snapshot = nullptr; + } + } + + ~snapshot() { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + } + + snapshot_t* c_ptr() const { + return m_snapshot; + } + + filter_iterator begin(); + + filter_iterator end(); +private: + const world& m_world; + snapshot_t *m_snapshot; +}; + +} // namespace flecs + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for iterating over tables that match a filter +//////////////////////////////////////////////////////////////////////////////// + +class filter_iterator +{ +public: + filter_iterator(ecs_iter_next_action_t action) + : m_world(nullptr) + , m_has_next(false) + , m_iter{ } + , m_action(action) { } + + filter_iterator(const world& world, ecs_iter_next_action_t action) + : m_world( world.c_ptr() ) + , m_iter( ecs_filter_iter(m_world, NULL) ) + , m_action(action) + { + m_has_next = m_action(&m_iter); + } + + filter_iterator(const world& world, const snapshot& snapshot, ecs_iter_next_action_t action) + : m_world( world.c_ptr() ) + , m_iter( ecs_snapshot_iter(snapshot.c_ptr(), NULL) ) + , m_action(action) + { + m_has_next = m_action(&m_iter); + } + + bool operator!=(filter_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + filter_iterator& operator++() { + m_has_next = m_action(&m_iter); + return *this; + } + +private: + world_t *m_world; + bool m_has_next; + ecs_iter_t m_iter; + ecs_iter_next_action_t m_action; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Tree iterator +//////////////////////////////////////////////////////////////////////////////// + +class tree_iterator +{ +public: + tree_iterator() + : m_has_next(false) + , m_iter{ } { } + + tree_iterator(flecs::world_t *world, const flecs::entity_t entity) + : m_iter( ecs_scope_iter(world, entity )) + { + m_has_next = ecs_scope_next(&m_iter); + } + + bool operator!=(tree_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + tree_iterator& operator++() { + m_has_next = ecs_scope_next(&m_iter); + return *this; + } + +private: + bool m_has_next; + ecs_iter_t m_iter; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a world-based filter iterator +//////////////////////////////////////////////////////////////////////////////// + +class world_filter { +public: + world_filter(const world& world) + : m_world( world ) { } + + inline filter_iterator begin() const { + return filter_iterator(m_world, ecs_filter_next); + } + + inline filter_iterator end() const { + return filter_iterator(ecs_filter_next); + } + +private: + const world& m_world; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a snapshot-based filter iterator +//////////////////////////////////////////////////////////////////////////////// + +class snapshot_filter { +public: + snapshot_filter(const world& world, const snapshot& snapshot) + : m_world( world ) + , m_snapshot( snapshot ) { } + + inline filter_iterator begin() const { + return filter_iterator(m_world, m_snapshot, ecs_snapshot_next); + } + + inline filter_iterator end() const { + return filter_iterator(ecs_snapshot_next); + } + +private: + const world& m_world; + const snapshot& m_snapshot; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a child table iterator +//////////////////////////////////////////////////////////////////////////////// + +class child_iterator { +public: + child_iterator(const flecs::entity_view& entity) + : m_world( entity.world().c_ptr() ) + , m_parent( entity.id() ) { } + + inline tree_iterator begin() const { + return tree_iterator(m_world, m_parent); + } + + inline tree_iterator end() const { + return tree_iterator(); + } + +private: + flecs::world_t *m_world; + flecs::entity_t m_parent; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Filter fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +inline filter_iterator snapshot::begin() { + return filter_iterator(m_world, *this, ecs_snapshot_next); +} + +inline filter_iterator snapshot::end() { + return filter_iterator(ecs_snapshot_next); +} + +} + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Persistent queries +//////////////////////////////////////////////////////////////////////////////// + +class query_base { +public: + query_base() + : m_world(nullptr) + , m_query(nullptr) { } + + query_base(world_t *world, query_t *query = nullptr) + : m_world(world) + , m_query(query) { } + + /** Get pointer to C query object. + */ + query_t* c_ptr() const { + return m_query; + } + + /** 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 (sub)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * 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 <typename T> + void order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_query_order_by(m_world, m_query, + flecs::_::cpp_type<T>::id(m_world), + reinterpret_cast<ecs_order_by_action_t>(compare)); + } + + /** Sort the output of a query. + * Same as order_by<T>, but with component identifier. + * + * @param component The component used to sort. + * @param compare The compare function used to sort the components. + */ + void order_by(flecs::entity component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + ecs_query_order_by(m_world, m_query, component.id(), compare); + } + + /** Group and sort matched tables. + * Similar yo ecs_query_order_by, but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * 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 rank The rank action. + */ + template <typename T> + void group_by(ecs_group_by_action_t callback) { + ecs_query_group_by(m_world, m_query, + flecs::_::cpp_type<T>::id(m_world), callback); + } + + /** Group and sort matched tables. + * Same as group_by<T>, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param rank The rank action. + */ + void group_by(flecs::entity component, ecs_group_by_action_t callback) { + ecs_query_group_by(m_world, m_query, component.id(), callback); + } + + /** Returns whether the query data changed since the last iteration. + * This operation must be invoked before obtaining the iterator, as this will + * reset the changed state. The operation will return true after: + * - new entities have been matched with + * - matched entities were deleted + * - matched components were changed + * + * @return true if entities changed, otherwise false. + */ + bool changed() { + return ecs_query_changed(m_query); + } + + /** Returns whether query is orphaned. + * When the parent query of a subquery is deleted, it is left in an orphaned + * state. The only valid operation on an orphaned query is deleting it. Only + * subqueries can be orphaned. + * + * @return true if query is orphaned, otherwise false. + */ + bool orphaned() { + return ecs_query_orphaned(m_query); + } + + /** Free the query. + */ + void destruct() { + ecs_query_free(m_query); + m_world = nullptr; + m_query = nullptr; + } + + template <typename Func> + void iter(Func&& func) const { + ecs_iter_t it = ecs_query_iter(m_query); + while (ecs_query_next(&it)) { + _::iter_invoker<Func>(func).invoke(&it); + } + } + + template <typename Func> + void each_term(const Func& func) { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); + + for (int i = 0; i < f->term_count; i ++) { + flecs::term t(m_world, f->terms[i]); + func(t); + } + } + + flecs::term term(int32_t index) { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs::term(m_world, f->terms[index]); + } + + int32_t term_count() { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + return f->term_count; + } + + flecs::string str() { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + char *result = ecs_filter_str(m_world, f); + return flecs::string(result); + } + +protected: + world_t *m_world; + query_t *m_query; +}; + + +template<typename ... Components> +class query : public query_base { + using Terms = typename _::term_ptrs<Components...>::array; + +public: + query() { } + + query(world_t *world, query_t *q) + : query_base(world, q) { } + + explicit query(const world& world, const char *expr = nullptr) + : query_base(world.c_ptr()) + { + auto qb = world.query_builder<Components ...>() + .expr(expr); + + if (!expr) { + qb.substitute_default(); + } + + m_query = qb; + } + + explicit query(const world& world, query_base& parent, const char *expr = nullptr) + : query_base(world.c_ptr()) + { + auto qb = world.query_builder<Components ...>() + .parent(parent) + .expr(expr); + + if (!expr) { + qb.substitute_default(); + } + + m_query = qb; + } + + template <typename Func> + void each(Func&& func) const { + iterate<_::each_invoker>(std::forward<Func>(func), ecs_query_next); + } + + template <typename Func> + void each_worker(int32_t stage_current, int32_t stage_count, Func&& func) const { + iterate<_::each_invoker>(std::forward<Func>(func), + ecs_query_next_worker, stage_current, stage_count); + } + + template <typename Func> + void iter(Func&& func) const { + iterate<_::iter_invoker>(std::forward<Func>(func), ecs_query_next); + } + + template <typename Func> + void iter_worker(int32_t stage_current, int32_t stage_count, Func&& func) const { + iterate<_::iter_invoker>(std::forward<Func>(func), + ecs_query_next_worker, stage_current, stage_count); + } + + template <typename Func> + ECS_DEPRECATED("use each or iter") + void action(Func&& func) const { + iterate<_::action_invoker>(std::forward<Func>(func), ecs_query_next); + } + +private: + template < template<typename Func, typename ... Comps> class Invoker, typename Func, typename NextFunc, typename ... Args> + void iterate(Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = ecs_query_iter(m_query); + while (next(&it, std::forward<Args>(args)...)) { + Invoker<Func, Components...>(func).invoke(&it); + } + } +}; + +} // namespace flecs + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Fluent interface to run a system manually +//////////////////////////////////////////////////////////////////////////////// + +class system_runner_fluent { +public: + system_runner_fluent( + world_t *world, + entity_t id, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + void *param) + : m_stage(world) + , m_id(id) + , m_delta_time(delta_time) + , m_param(param) + , m_filter() + , m_offset(0) + , m_limit(0) + , m_stage_current(stage_current) + , m_stage_count(stage_count) { } + + template <typename F> + system_runner_fluent& filter(const F& f) { + m_filter = f; + return *this; + } + + system_runner_fluent& offset(int32_t offset) { + m_offset = offset; + return *this; + } + + system_runner_fluent& limit(int32_t limit) { + m_limit = limit; + return *this; + } + + system_runner_fluent& stage(flecs::world& stage) { + m_stage = stage.c_ptr(); + return *this; + } + + ~system_runner_fluent() { + if (m_stage_count) { + ecs_run_worker( + m_stage, m_id, m_stage_current, m_stage_count, m_delta_time, + m_param); + } else { + ecs_run_w_filter( + m_stage, m_id, m_delta_time, m_offset, m_limit, + m_filter.c_ptr(), m_param); + } + } + +private: + world_t *m_stage; + entity_t m_id; + FLECS_FLOAT m_delta_time; + void *m_param; + flecs::filter<> m_filter; + int32_t m_offset; + int32_t m_limit; + int32_t m_stage_current; + int32_t m_stage_count; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Register a system with Flecs +//////////////////////////////////////////////////////////////////////////////// + +template<typename ... Components> +class system : public entity +{ +public: + explicit system() + : entity() { } + + explicit system(flecs::world_t *world, flecs::entity_t id) + : entity(world, id) { } + + template <typename T> + void order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + this->order_by(flecs::_::cpp_type<T>::id(m_world), + reinterpret_cast<ecs_order_by_action_t>(compare)); + } + + void order_by(flecs::entity_t comp, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + ecs_query_t *q = query().c_ptr(); + ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_order_by(m_world, q, comp, compare); + } + + template <typename T> + void group_by(int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + this->group_by(flecs::_::cpp_type<T>::id(m_world), rank); + } + + void group_by(flecs::entity_t comp, int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + ecs_query_t *q = query().c_ptr(); + ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_group_by(m_world, q, comp, rank); + } + + /** 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. + */ + void interval(FLECS_FLOAT interval) { + ecs_set_interval(m_world, m_id, interval); + } + + /** 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. + */ + void rate(const flecs::entity& tick_source, int32_t rate) { + ecs_set_rate(m_world, m_id, rate, tick_source.id()); + } + + /** 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. + */ + void rate(int32_t rate) { + ecs_set_rate(m_world, m_id, rate, 0); + } + + /** Get interval. + * Get interval at which the system is running. + * + * @return The timer entity. + */ + FLECS_FLOAT interval() { + return ecs_get_interval(m_world, m_id); + } + + void enable() { + ecs_enable(m_world, m_id, true); + } + + void disable() { + ecs_enable(m_world, m_id, false); + } + + void ctx(void *ctx) { + if (ecs_has(m_world, m_id, EcsSystem)) { + ecs_system_desc_t desc = {}; + desc.entity.entity = m_id; + desc.ctx = ctx; + ecs_system_init(m_world, &desc); + } else { + ecs_trigger_desc_t desc = {}; + desc.entity.entity = m_id; + desc.ctx = ctx; + ecs_trigger_init(m_world, &desc); + } + } + + void* ctx() const { + if (ecs_has(m_world, m_id, EcsSystem)) { + return ecs_get_system_ctx(m_world, m_id); + } else { + return ecs_get_trigger_ctx(m_world, m_id); + } + } + + ECS_DEPRECATED("use interval") + void period(FLECS_FLOAT period) { + this->interval(period); + } + + ECS_DEPRECATED("use interval") + void set_period(FLECS_FLOAT period) const { + this->interval(period); + } + + ECS_DEPRECATED("use ctx(void*)") + void set_context(void *ptr) { + ctx(ptr); + } + + ECS_DEPRECATED("use void* ctx()") + void* get_context() const { + return ctx(); + } + + query_base query() const { + return query_base(m_world, ecs_get_system_query(m_world, m_id)); + } + + system_runner_fluent run(FLECS_FLOAT delta_time = 0.0f, void *param = nullptr) const { + return system_runner_fluent(m_world, m_id, 0, 0, delta_time, param); + } + + system_runner_fluent run_worker( + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time = 0.0f, + void *param = nullptr) const + { + return system_runner_fluent( + m_world, m_id, stage_current, stage_count, delta_time, param); + } +}; + +} // namespace flecs + +namespace flecs +{ + +template<typename ... Components> +class observer : public entity +{ +public: + explicit observer() + : entity() { } + + explicit observer(flecs::world_t *world, flecs::entity_t id) + : entity(world, id) { } + + void ctx(void *ctx) { + ecs_observer_desc_t desc = {}; + desc.entity.entity = m_id; + desc.ctx = ctx; + ecs_observer_init(m_world, &desc); + } + + void* ctx() const { + return ecs_get_observer_ctx(m_world, m_id); + } +}; + +} // namespace flecs + + + +namespace flecs +{ + +namespace _ +{ + +template <typename T> +inline void ctor_world_entity_impl( + ecs_world_t* world, ecs_entity_t, const ecs_entity_t* ids, void *ptr, + size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast<T*>(ptr); + flecs::world w(world); + for (int i = 0; i < count; i ++) { + flecs::entity e(world, ids[i]); + FLECS_PLACEMENT_NEW(&arr[i], T(w, e)); + } +} + +} // _ +} // flecs + +namespace flecs +{ + +inline flecs::entity id::entity() const { + ecs_assert(!is_pair(), ECS_INVALID_OPERATION, NULL); + ecs_assert(!role(), ECS_INVALID_OPERATION, NULL); + return flecs::entity(m_world, m_id); +} + +inline flecs::entity id::role() const { + return flecs::entity(m_world, m_id & ECS_ROLE_MASK); +} + +inline flecs::entity id::relation() const { + ecs_assert(is_pair(), ECS_INVALID_OPERATION, NULL); + + flecs::entity_t e = ECS_PAIR_RELATION(m_id); + if (m_world) { + return flecs::entity(m_world, ecs_get_alive(m_world, e)); + } else { + return flecs::entity(e); + } +} + +inline flecs::entity id::object() const { + flecs::entity_t e = ECS_PAIR_OBJECT(m_id); + if (m_world) { + return flecs::entity(m_world, ecs_get_alive(m_world, e)); + } else { + return flecs::entity(m_world, e); + } +} + +inline flecs::entity id::add_role(flecs::id_t role) const { + return flecs::entity(m_world, m_id | role); +} + +inline flecs::entity id::remove_role(flecs::id_t role) const { + (void)role; + ecs_assert((m_id & ECS_ROLE_MASK) == role, ECS_INVALID_PARAMETER, NULL); + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_role() const { + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_generation() const { + return flecs::entity(m_world, static_cast<uint32_t>(m_id)); +} + +inline entity id::lo() const { + return flecs::entity(m_world, ecs_entity_t_lo(m_id)); +} + +inline entity id::hi() const { + return flecs::entity(m_world, ecs_entity_t_hi(m_id)); +} + +inline entity id::comb(entity_view lo, entity_view hi) { + return flecs::entity(lo.world(), + ecs_entity_t_comb(lo.id(), hi.id())); +} + +} + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Entity range, allows for operating on a range of consecutive entities +//////////////////////////////////////////////////////////////////////////////// + +class ECS_DEPRECATED("do not use") entity_range final { +public: + entity_range(const world& world, int32_t count) + : m_world(world.c_ptr()) + , m_ids( ecs_bulk_new_w_type(m_world, nullptr, count)) { } + + entity_range(const world& world, int32_t count, flecs::type type) + : m_world(world.c_ptr()) + , m_ids( ecs_bulk_new_w_type(m_world, type.c_ptr(), count)) { } + +private: + world_t *m_world; + const entity_t *m_ids; +}; + +template <typename T> +flecs::entity ref<T>::entity() const { + return flecs::entity(m_world, m_entity); +} + +template <typename Base> +inline const Base& entity_builder<Base>::add(const type& type) const { + ecs_add_type(this->base_world(), this->base_id(), type.c_ptr()); + return *this; +} + +template <typename Base> +inline const Base& entity_builder<Base>::remove(const type& type) const { + ecs_remove_type(this->base_world(), this->base_id(), type.c_ptr()); + return *this; +} + +template <typename Base> +inline const Base& entity_builder<Base>::add_owned(const type& type) const { + return add_owned(type.id()); +} + +template <typename Base> +inline const Base& entity_builder<Base>::add_switch(const type& sw) const { + return add_switch(sw.id()); +} + +template <typename Base> +inline const Base& entity_builder<Base>::remove_switch(const type& sw) const { + return remove_switch(sw.id()); +} + +template <typename Base> +template <typename Func, if_t< is_callable<Func>::value > > +inline const Base& entity_builder<Base>::set(const Func& func) const { + _::entity_with_invoker<Func>::invoke_get_mut( + this->base_world(), this->base_id(), func); + return *this; +} + +template <typename Base> +template <typename T> +inline const Base& entity_builder<Base>::component() const { + component_for_id<T>(this->base_world(), this->base_id()); + return *this; +} + +inline bool entity_view::has_switch(const flecs::type& type) const { + return ecs_has_entity(m_world, m_id, flecs::Switch | type.id()); +} + +inline flecs::entity entity_view::get_case(const flecs::type& sw) const { + return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw.id())); +} + +inline flecs::entity entity_view::get_case(flecs::id_t sw) const { + return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw)); +} + +template <typename T> +inline flecs::entity entity_view::get_case() const { + return get_case(_::cpp_type<T>::id(m_world)); +} + +inline flecs::entity entity_view::get_object( + flecs::entity_t relation, + int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_object(m_world, m_id, relation, index)); +} + +inline flecs::entity entity_view::mut(const flecs::world& stage) const { + ecs_assert(!stage.is_readonly(), ECS_INVALID_PARAMETER, + "cannot use readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(stage.c_ptr()); +} + +/** Same as mut(world), but for iterator. + * This operation allows for the construction of a mutable entity handle + * from an iterator. + * + * @param stage An created for the current stage. + * @return An entity handle that allows for mutations in the current stage. + */ +inline flecs::entity entity_view::mut(const flecs::iter& it) const { + ecs_assert(!it.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use iterator created for readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(it.world().c_ptr()); +} + +/** 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 stage An created for the current stage. + * @return An entity handle that allows for mutations in the current stage. + */ +inline flecs::entity entity_view::mut(const flecs::entity_view& e) const { + ecs_assert(!e.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use entity created for readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(e.m_world); +} + +inline flecs::entity entity_view::set_stage(world_t *stage) { + m_world = stage; + return flecs::entity(m_world, m_id); +} + +inline flecs::type entity_view::type() const { + return flecs::type(m_world, ecs_get_type(m_world, m_id)); +} + +inline flecs::type entity_view::to_type() const { + ecs_type_t type = ecs_type_from_id(m_world, m_id); + return flecs::type(m_world, type); +} + +inline child_iterator entity_view::children() const { + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return flecs::child_iterator(*this); +} + +template <typename Func> +inline void entity_view::each(const Func& func) const { + const ecs_vector_t *type = ecs_get_type(m_world, m_id); + if (!type) { + return; + } + + const ecs_id_t *ids = static_cast<ecs_id_t*>( + _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); + int32_t count = ecs_vector_count(type); + + for (int i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + flecs::id ent(m_world, id); + func(ent); + + // Case is not stored in type, so handle separately + if ((id & ECS_ROLE_MASK) == flecs::Switch) { + ent = flecs::id( + m_world, flecs::Case | ecs_get_case( + m_world, m_id, ent.object().id())); + func(ent); + } + } +} + +template <typename Func> +inline void entity_view::match(id_t pattern, const Func& func) const { + const ecs_vector_t *type = ecs_get_type(m_world, m_id); + if (!type) { + return; + } + + id_t *ids = static_cast<ecs_id_t*>( + _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); + int32_t cur = 0; + + while (-1 != (cur = ecs_type_index_of(type, cur, pattern))) { + flecs::id ent(m_world, ids[cur]); + func(ent); + cur ++; + } +} + +template <typename Func> +inline void entity_view::each(const flecs::entity_view& rel, const Func& func) const { + return this->match(ecs_pair(rel, flecs::Wildcard), [&](flecs::id id) { + flecs::entity obj = id.object(); + func(obj); + }); +} + +template <typename Func, if_t< is_callable<Func>::value > > +inline bool entity_view::get(const Func& func) const { + return _::entity_with_invoker<Func>::invoke_get(m_world, m_id, func); +} + +template <typename T> +inline flecs::entity entity_view::get_parent() { + return flecs::entity(m_world, ecs_get_parent_w_entity(m_world, m_id, + _::cpp_type<T>::id(m_world))); +} + +inline flecs::entity entity_view::get_parent(flecs::entity_view e) { + return flecs::entity(m_world, + ecs_get_parent_w_entity(m_world, m_id, e.id())); +} + +inline flecs::entity entity_view::lookup(const char *path) const { + auto id = ecs_lookup_path_w_sep(m_world, m_id, path, "::", "::", false); + return flecs::entity(m_world, id); +} + +} + +namespace flecs +{ + +inline flecs::entity iter::system() const { + return flecs::entity(m_iter->world, m_iter->system); +} + + inline flecs::entity iter::self() const { + return flecs::entity(m_iter->world, m_iter->self); +} + +inline flecs::world iter::world() const { + return flecs::world(m_iter->world); +} + +inline flecs::entity iter::entity(size_t row) const { + ecs_assert(row < static_cast<size_t>(m_iter->count), ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + if (!this->world().is_readonly()) { + return flecs::entity(m_iter->entities[row]) + .mut(this->world()); + } else { + return flecs::entity(this->world().c_ptr(), m_iter->entities[row]); + } +} + +/* Obtain column source (0 if self) */ +template <typename Base> +inline flecs::entity iter_deprecated<Base>::column_source(int32_t col) const { + return flecs::entity(iter()->world, ecs_term_source(iter(), col)); +} + +/* Obtain component/tag entity of column */ +template <typename Base> +inline flecs::entity iter_deprecated<Base>::column_entity(int32_t col) const { + return flecs::entity(iter()->world, ecs_term_id(iter(), col)); +} + +/* Obtain type of column */ +template <typename Base> +inline type iter_deprecated<Base>::column_type(int32_t col) const { + return flecs::type(iter()->world, ecs_column_type(iter(), col)); +} + +/* Obtain type of table being iterated over */ +template <typename Base> +inline type iter_deprecated<Base>::table_type() const { + return flecs::type(iter()->world, ecs_iter_type(iter())); +} + +template <typename T> +inline column<T>::column(iter &iter, int32_t index) { + *this = iter.term<T>(index); +} + +inline flecs::entity iter::term_source(int32_t index) const { + return flecs::entity(m_iter->world, ecs_term_source(m_iter, index)); +} + +inline flecs::entity iter::term_id(int32_t index) const { + return flecs::entity(m_iter->world, ecs_term_id(m_iter, index)); +} + +/* Obtain type of iter */ +inline flecs::type iter::type() const { + return flecs::type(m_iter->world, ecs_iter_type(m_iter)); +} + +} // namespace flecs + +namespace flecs +{ + +// emplace for T(flecs::entity, Args...) +template <typename T, typename ... Args, if_t< + std::is_constructible<actual_type_t<T>, flecs::entity, Args...>::value >> +inline void emplace(world_t *world, id_t entity, Args&&... args) { + flecs::entity self(world, entity); + emplace<T>(world, entity, self, std::forward<Args>(args)...); +} + +/** Get id from a type. */ +template <typename T> +inline flecs::id world::id() const { + return flecs::id(m_world, _::cpp_type<T>::id(m_world)); +} + +template <typename ... Args> +inline flecs::id world::id(Args&&... args) const { + return flecs::id(m_world, std::forward<Args>(args)...); +} + +template <typename R, typename O> +inline flecs::id world::pair() const { + return flecs::id( + m_world, + ecs_pair( + _::cpp_type<R>::id(m_world), + _::cpp_type<O>::id(m_world))); +} + +template <typename R> +inline flecs::id world::pair(entity_t o) const { + return flecs::id( + m_world, + ecs_pair( + _::cpp_type<R>::id(m_world), + o)); +} + +inline flecs::id world::pair(entity_t r, entity_t o) const { + return flecs::id( + m_world, + ecs_pair(r, o)); +} + +inline filter_iterator world::begin() const { + return filter_iterator(*this, ecs_filter_next); +} + +inline filter_iterator world::end() const { + return filter_iterator(ecs_filter_next); +} + +/** All entities created in function are created in scope. All operations + * called in function (such as lookup) are relative to scope. + */ +template <typename Func> +void scope(id_t parent, const Func& func); + +inline void world::init_builtin_components() { + pod_component<Component>("flecs::core::Component"); + pod_component<Type>("flecs::core::Type"); + pod_component<Identifier>("flecs::core::Identifier"); + pod_component<Trigger>("flecs::core::Trigger"); + pod_component<Observer>("flecs::core::Observer"); + pod_component<Query>("flecs::core::Query"); + + pod_component<TickSource>("flecs::system::TickSource"); + pod_component<RateFilter>("flecs::timer::RateFilter"); + pod_component<Timer>("flecs::timer::Timer"); +} + +template <typename T> +inline flecs::entity world::use(const char *alias) { + entity_t e = _::cpp_type<T>::id(m_world); + const char *name = alias; + if (!name) { + // If no name is defined, use the entity name without the scope + name = ecs_get_name(m_world, e); + } + ecs_use(m_world, e, name); + return flecs::entity(m_world, e); +} + +inline flecs::entity world::use(const char *name, const char *alias) { + entity_t e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", true); + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_use(m_world, e, alias); + return flecs::entity(m_world, e); +} + +inline void world::use(flecs::entity e, const char *alias) { + entity_t eid = e.id(); + const char *name = alias; + if (!name) { + // If no name is defined, use the entity name without the scope + ecs_get_name(m_world, eid); + } + ecs_use(m_world, eid, alias); +} + +inline flecs::entity world::set_scope(const flecs::entity& s) const { + return flecs::entity(ecs_set_scope(m_world, s.id())); +} + +inline flecs::entity world::get_scope() const { + return flecs::entity(ecs_get_scope(m_world)); +} + +inline entity world::lookup(const char *name) const { + auto e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", true); + return flecs::entity(*this, e); +} + +template <typename T> +T* world::get_mut() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.get_mut<T>(); +} + +template <typename T> +void world::modified() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.modified<T>(); +} + +template <typename T, typename Func> +void world::patch(const Func& func) const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + e.patch<T>(func); +} + +template <typename T> +const T* world::get() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.get<T>(); +} + +template <typename T> +bool world::has() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.has<T>(); +} + +template <typename T> +void world::add() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + e.add<T>(); +} + +template <typename T> +void world::remove() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + e.remove<T>(); +} + +inline void world::set_pipeline(const flecs::pipeline& pip) const { + ecs_set_pipeline(m_world, pip.id()); +} + +template <typename T> +inline flecs::entity world::singleton() { + return flecs::entity(m_world, _::cpp_type<T>::id(m_world)); +} + +template <typename... Args> +inline flecs::entity world::entity(Args &&... args) const { + return flecs::entity(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::entity world::prefab(Args &&... args) const { + return flecs::prefab(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::type world::type(Args &&... args) const { + return flecs::type(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::pipeline world::pipeline(Args &&... args) const { + return flecs::pipeline(*this, std::forward<Args>(args)...); +} + +inline flecs::system<> world::system(flecs::entity e) const { + return flecs::system<>(m_world, e); +} + +template <typename... Comps, typename... Args> +inline flecs::system_builder<Comps...> world::system(Args &&... args) const { + return flecs::system_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::observer_builder<Comps...> world::observer(Args &&... args) const { + return flecs::observer_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::filter<Comps...> world::filter(Args &&... args) const { + return flecs::filter<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::filter_builder<Comps...> world::filter_builder(Args &&... args) const { + return flecs::filter_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::query<Comps...> world::query(Args &&... args) const { + return flecs::query<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::query_builder<Comps...> world::query_builder(Args &&... args) const { + return flecs::query_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::term world::term(Args &&... args) const { + return flecs::term(*this, std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +inline flecs::term world::term(Args &&... args) const { + return flecs::term(*this, std::forward<Args>(args)...).id<T>(); +} + +template <typename R, typename O, typename... Args> +inline flecs::term world::term(Args &&... args) const { + return flecs::term(*this, std::forward<Args>(args)...).id<R, O>(); +} + +template <typename Module, typename... Args> +inline flecs::entity world::module(Args &&... args) const { + return flecs::module<Module>(*this, std::forward<Args>(args)...); +} + +template <typename Module> +inline flecs::entity world::import() { + return flecs::import<Module>(*this); +} + +template <typename T, typename... Args> +inline flecs::entity world::component(Args &&... args) const { + return flecs::component<T>(*this, std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +inline flecs::entity world::pod_component(Args &&... args) const { + return flecs::pod_component<T>(*this, std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +inline flecs::entity world::relocatable_component(Args &&... args) const { + return flecs::relocatable_component<T>(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::snapshot world::snapshot(Args &&... args) const { + return flecs::snapshot(*this, std::forward<Args>(args)...); +} + +template <typename T, typename Func> +inline void world::each(Func&& func) const { + ecs_term_t t = {}; + t.id = _::cpp_type<T>::id(); + ecs_iter_t it = ecs_term_iter(m_world, &t); + + while (ecs_term_next(&it)) { + _::each_invoker<Func, T>(func).invoke(&it); + } +} + +template <typename Func> +inline void world::each(flecs::id_t term_id, Func&& func) const { + ecs_term_t t = {}; + t.id = term_id; + ecs_iter_t it = ecs_term_iter(m_world, &t); + + while (ecs_term_next(&it)) { + _::each_invoker<Func>(func).invoke(&it); + } +} + +namespace _ { + +// Each with entity parameter +template<typename Func, typename ... Args> +struct filter_invoker_w_ent; + +template<typename Func, typename E, typename ... Args> +struct filter_invoker_w_ent<Func, arg_list<E, Args ...> > +{ + filter_invoker_w_ent(const flecs::world& world, Func&& func) { + flecs::filter<Args ...> f(world); + f.each(std::move(func)); + } +}; + +// Each without entity parameter +template<typename Func, typename ... Args> +struct filter_invoker_no_ent; + +template<typename Func, typename ... Args> +struct filter_invoker_no_ent<Func, arg_list<Args ...> > +{ + filter_invoker_no_ent(const flecs::world& world, Func&& func) { + flecs::filter<Args ...> f(world); + f.each(std::move(func)); + } +}; + +// Switch between function with & without entity parameter +template<typename Func, typename T = int> +class filter_invoker; + +template <typename Func> +class filter_invoker<Func, if_t<is_same<first_arg_t<Func>, flecs::entity>::value> > { +public: + filter_invoker(const flecs::world& world, Func&& func) { + filter_invoker_w_ent<Func, arg_list_t<Func>>(world, std::move(func)); + } +}; + +template <typename Func> +class filter_invoker<Func, if_not_t<is_same<first_arg_t<Func>, flecs::entity>::value> > { +public: + filter_invoker(const flecs::world& world, Func&& func) { + filter_invoker_no_ent<Func, arg_list_t<Func>>(world, std::move(func)); + } +}; + +} + +template <typename Func> +inline void world::each(Func&& func) const { + _::filter_invoker<Func> f_invoker(*this, std::move(func)); +} + +} // namespace flecs + +namespace flecs +{ + +template<typename Base> +inline Base& term_builder_i<Base>::id(const flecs::type& type) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = type.id(); + return *this; +} + +template <typename ... Components> +inline filter_builder_base<Components...>::operator filter<Components ...>() const { + ecs_filter_t filter = *this; + return flecs::filter<Components...>(m_world, &filter); +} + +template <typename ... Components> +inline filter_builder<Components ...>::operator filter<>() const { + ecs_filter_t filter = *this; + return flecs::filter<>(this->m_world, &filter); +} + +template <typename ... Components> +inline filter<Components ...> filter_builder_base<Components...>::build() const { + ecs_filter_t filter = *this; + return flecs::filter<Components...>(m_world, &filter); +} + +template <typename ... Components> +inline query_builder_base<Components...>::operator query<Components ...>() const { + ecs_query_t *query = *this; + return flecs::query<Components...>(m_world, query); +} + +template <typename ... Components> +inline query_builder<Components ...>::operator query<>() const { + ecs_query_t *query = *this; + return flecs::query<>(this->m_world, query); +} + +template <typename ... Components> +inline query<Components ...> query_builder_base<Components...>::build() const { + ecs_query_t *query = *this; + return flecs::query<Components...>(m_world, query); +} + +template <typename Base, typename ... Components> +inline Base& query_builder_i<Base, Components ...>::parent(const query_base& parent) { + m_desc->parent = parent.c_ptr(); + return *static_cast<Base*>(this); +} + +template <typename ... Components> +template <typename Func> +inline system<Components ...> system_builder<Components...>::action(Func&& func) const { + flecs::entity_t system = build<action_invoker_t<Func>>(std::forward<Func>(func), false); + return flecs::system<Components...>(m_world, system); +} + +template <typename ... Components> +template <typename Func> +inline system<Components ...> system_builder<Components...>::iter(Func&& func) const { + using Invoker = typename _::iter_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t system = build<Invoker>(std::forward<Func>(func), false); + return flecs::system<Components...>(m_world, system); +} + +template <typename ... Components> +template <typename Func> +inline system<Components ...> system_builder<Components...>::each(Func&& func) const { + using Invoker = typename _::each_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t system = build<Invoker>(std::forward<Func>(func), true); + return flecs::system<Components...>(m_world, system); +} + +template <typename ... Components> +template <typename Func> +inline observer<Components ...> observer_builder<Components...>::iter(Func&& func) const { + using Invoker = typename _::iter_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t observer = build<Invoker>(std::forward<Func>(func), false); + return flecs::observer<Components...>(m_world, observer); +} + +template <typename ... Components> +template <typename Func> +inline observer<Components ...> observer_builder<Components...>::each(Func&& func) const { + using Invoker = typename _::each_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t observer = build<Invoker>(std::forward<Func>(func), true); + return flecs::observer<Components...>(m_world, observer); +} + +} +#endif +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs.h b/fggl/ecs2/flecs/include/flecs.h new file mode 100644 index 0000000000000000000000000000000000000000..19ae2be9eb846b846bf81bcc9e9d4aa779d44a58 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs.h @@ -0,0 +1,4228 @@ +/** + * @file flecs.h + * @brief Flecs public API. + * + * This file contains the public API for Flecs. + */ + +#ifndef FLECS_H +#define FLECS_H + +/* FLECS_LEGACY should be defined when building for C89 */ +// #define FLECS_LEGACY + +/* FLECS_NO_DEPRECATED_WARNINGS disables deprecated warnings */ +#define FLECS_NO_DEPRECATED_WARNINGS + +/* FLECS_NO_CPP should be defined when building for C++ without the C++ API */ +// #define FLECS_NO_CPP + +/* FLECS_CUSTOM_BUILD should be defined when manually selecting features */ +// #define FLECS_CUSTOM_BUILD + +/* FLECS_SANITIZE enables expensive checks that can detect issues early */ +#ifndef NDEBUG +#define FLECS_SANITIZE +#endif + +/* If this is a regular, non-custom build, build all modules and addons. */ +#ifndef FLECS_CUSTOM_BUILD +/* Modules */ +#define FLECS_SYSTEM +#define FLECS_PIPELINE +#define FLECS_TIMER + +/* Addons */ +#define FLECS_BULK +#define FLECS_MODULE +#define FLECS_PARSER +#define FLECS_PLECS +#define FLECS_QUEUE +#define FLECS_SNAPSHOT +#define FLECS_DIRECT_ACCESS +#define FLECS_STATS +#endif // ifndef FLECS_CUSTOM_BUILD + +/* Unconditionally include deprecated definitions until the rest of the codebase + * has caught up */ +#define FLECS_DEPRECATED + +/* Set to double or int to increase accuracy of time keeping. Note that when + * using an integer type, an application has to provide the delta_time values + * to the progress() function, as the code that measures time requires a + * floating point type. */ +#ifndef FLECS_FLOAT +#define FLECS_FLOAT float +#endif // FLECS_FLOAT + +#include "flecs/private/api_defines.h" +#include "flecs/private/log.h" /* Logging API */ +#include "flecs/private/vector.h" /* Vector datatype */ +#include "flecs/private/map.h" /* Map */ +#include "flecs/private/strbuf.h" /* String builder */ +#include "flecs/os_api.h" /* Abstraction for operating system functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup api_types Basic API types + * @{ + */ + +/** Pointer object returned by API. */ +typedef void ecs_object_t; + +/** An id. Ids are the things that can be added to an entity. An id can be an + * entity or pair, and can have an optional role. */ +typedef uint64_t ecs_id_t; + +/** An entity identifier. */ +typedef ecs_id_t ecs_entity_t; + +/** A vector containing component identifiers used to describe a type. */ +typedef const ecs_vector_t* ecs_type_t; + +/** A world is the container for all ECS data and supporting features. */ +typedef struct ecs_world_t ecs_world_t; + +/** A query allows for cached iteration over ECS data */ +typedef struct ecs_query_t ecs_query_t; + +/** A filter allows for uncached, ad hoc iteration over ECS data */ +typedef struct ecs_filter_t ecs_filter_t; + +/** A trigger reacts to events matching a single filter term */ +typedef struct ecs_trigger_t ecs_trigger_t; + +/** An observer reacts to events matching multiple filter terms */ +typedef struct ecs_observer_t ecs_observer_t; + +/* An iterator lets an application iterate entities across tables. */ +typedef struct ecs_iter_t ecs_iter_t; + +/** Refs cache data that lets them access components faster than ecs_get. */ +typedef struct ecs_ref_t ecs_ref_t; + +/** @} */ + + + +/** + * @defgroup constants API constants + * @{ + */ + +/* Maximum number of components to add/remove in a single operation */ +#define ECS_MAX_ADD_REMOVE (32) + +/* Maximum number of terms cached in static arrays */ +#define ECS_TERM_CACHE_SIZE (8) + +/* Maximum number of terms in desc (larger, as these are temp objects) */ +#define ECS_TERM_DESC_CACHE_SIZE (16) + +/* Maximum number of events to set in static array of trigger descriptor */ +#define ECS_TRIGGER_DESC_EVENT_COUNT_MAX (8) + +/** @} */ + + +/** + * @defgroup function_types Function Types + * @{ + */ + +/** Action callback for systems and triggers */ +typedef void (*ecs_iter_action_t)( + ecs_iter_t *it); + +typedef bool (*ecs_iter_next_action_t)( + ecs_iter_t *it); + +/** Callback used for sorting components */ +typedef int (*ecs_order_by_action_t)( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +/** Callback used for ranking types */ +typedef int32_t (*ecs_group_by_action_t)( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + void *ctx); + +/** 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); + +/** @} */ + + +/** + * @defgroup filter_types Types used to describe filters, terms and triggers + * @{ + */ + +/** Set flags describe if & how a matched entity should be substituted */ +#define EcsDefaultSet (0) /* Default set, SuperSet|Self for This subject */ +#define EcsSelf (1) /* Select self (inclusive) */ +#define EcsSuperSet (2) /* Select superset until predicate match */ +#define EcsSubSet (4) /* Select subset until predicate match */ +#define EcsCascade (8) /* Use breadth-first ordering of relations */ +#define EcsAll (16) /* Walk full super/subset, regardless of match */ +#define EcsNothing (32) /* Select from nothing */ + +/** Specify read/write access for term */ +typedef enum ecs_inout_kind_t { + EcsInOutDefault, + EcsInOut, + EcsIn, + EcsOut +} ecs_inout_kind_t; + +/** Specifies whether term identifier is a variable or entity */ +typedef enum ecs_var_kind_t { + EcsVarDefault, /* Variable if name is all caps, otherwise an entity */ + EcsVarIsEntity, /* Term is an entity */ + EcsVarIsVariable /* Term is a variable */ +} ecs_var_kind_t; + +/** Type describing an operator used in an signature of a system signature */ +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; + +/** Substitution with set parameters. + * These parameters allow for substituting a term id with its super- or subsets + * for a specified relationship. This enables functionality such as selecting + * components from a base (IsA) or a parent (ChildOf) in a single term */ +typedef struct ecs_term_set_t { + ecs_entity_t relation; /* Relationship to substitute (default = IsA) */ + uint8_t mask; /* Substitute as self, subset, superset */ + int32_t min_depth; /* Min depth of subset/superset substitution */ + int32_t max_depth; /* Max depth of subset/superset substitution */ +} ecs_term_set_t; + +/** Type that describes a single identifier in a term */ +typedef struct ecs_term_id_t { + ecs_entity_t entity; /* Entity (default = This) */ + char *name; /* Name (default = ".") */ + ecs_var_kind_t var; /* Is id a variable (default yes if name is + * all caps & entity is 0) */ + ecs_term_set_t set; /* Set substitution parameters */ +} ecs_term_id_t; + +/** Type that describes a single column in the system signature */ +typedef struct ecs_term_t { + ecs_id_t id; /* Can be used instead of pred, args and role to + * set component/pair id. If not set, it will be + * computed from predicate, object. If set, the + * subject cannot be set, or be set to This. */ + + ecs_inout_kind_t inout; /* Access to contents matched with term */ + ecs_term_id_t pred; /* Predicate of term */ + ecs_term_id_t args[2]; /* Subject (0), object (1) of term */ + ecs_oper_kind_t oper; /* Operator of term */ + ecs_id_t role; /* Role of term */ + char *name; /* Name of term */ + + int32_t index; /* Computed term index in filter which takes + * into account folded OR terms */ + + bool move; /* When true, this signals to ecs_term_copy that + * the resources held by this term may be moved + * into the destination term. */ +} ecs_term_t; + +/* Deprecated -- do not use! */ +typedef enum ecs_match_kind_t { + EcsMatchDefault = 0, + EcsMatchAll, + EcsMatchAny, + EcsMatchExact +} ecs_match_kind_t; + +/** Filters alllow for ad-hoc quick filtering of entity tables. */ +struct ecs_filter_t { + ecs_term_t *terms; /* Array containing terms for filter */ + int32_t term_count; /* Number of elements in terms array */ + int32_t term_count_actual; /* Processed count, which folds OR terms */ + + ecs_term_t term_cache[ECS_TERM_CACHE_SIZE]; /* Cache for small filters */ + bool term_cache_used; + + bool match_this; /* Has terms that match EcsThis */ + bool match_only_this; /* Has only terms that match EcsThis */ + + char *name; /* Name of filter (optional) */ + char *expr; /* Expression of filter (if provided) */ + + /* Deprecated fields -- do not use! */ + ecs_type_t include; + ecs_type_t exclude; + ecs_match_kind_t include_kind; + ecs_match_kind_t exclude_kind; +}; + + +/** A trigger reacts to events matching a single term */ +struct ecs_trigger_t { + ecs_term_t term; /* Term describing the trigger condition id */ + + /* Trigger events */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + int32_t event_count; + + ecs_iter_action_t action; /* Callback */ + + void *ctx; /* Callback context */ + void *binding_ctx; /* Binding context (for language bindings) */ + + ecs_ctx_free_t ctx_free; /* Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + + ecs_entity_t entity; /* Trigger entity */ + ecs_entity_t self; /* Entity associated with observer */ + + uint64_t id; /* Internal id */ +}; + + +/* An observer reacts to events matching a filter */ +struct ecs_observer_t { + ecs_filter_t filter; + + /* Triggers created by observer (array size same as number of terms) */ + ecs_entity_t *triggers; + + /* Observer events */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + int32_t event_count; + + ecs_iter_action_t action; /* Callback */ + + void *ctx; /* Callback context */ + void *binding_ctx; /* Binding context (for language bindings) */ + + ecs_ctx_free_t ctx_free; /* Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + + ecs_entity_t entity; /* Observer entity */ + ecs_entity_t self; /* Entity associated with observer */ + + uint64_t id; /* Internal id */ +}; + +/** @} */ + + +#include "flecs/private/api_types.h" /* Supporting API types */ +#include "flecs/private/api_support.h" /* Supporting API functions */ +#include "flecs/type.h" /* Type API */ + + +/** + * @defgroup desc_types Types used for creating API constructs + * @{ + */ + +/** Used with ecs_entity_init */ +typedef struct ecs_entity_desc_t { + ecs_entity_t entity; /* Optional existing entity handle. */ + + const char *name; /* Name of the entity. If no entity is provided, an + * entity with this name will be looked up first. When + * an entity is provided, the name will be verified + * with the existing entity. */ + + const char *sep; /* Optional custom separator for hierarchical names */ + 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. */ + + /* Array of ids to add to the new or existing entity. */ + ecs_id_t add[ECS_MAX_ADD_REMOVE]; + + /* Array of ids to remove from the existing entity. */ + ecs_id_t remove[ECS_MAX_ADD_REMOVE]; + + /* String expression with components to add */ + const char *add_expr; + + /* String expression with components to remove */ + const char *remove_expr; +} ecs_entity_desc_t; + + +/** Used with ecs_component_init. */ +typedef struct ecs_component_desc_t { + ecs_entity_desc_t entity; /* Parameters for component entity */ + size_t size; /* Component size */ + size_t alignment; /* Component alignment */ +} ecs_component_desc_t; + + +/** Used with ecs_type_init. */ +typedef struct ecs_type_desc_t { + ecs_entity_desc_t entity; /* Parameters for type entity */ + ecs_id_t ids[ECS_MAX_ADD_REMOVE]; /* Ids to include in type */ + const char *ids_expr; /* Id expression to include in type */ +} ecs_type_desc_t; + + +/** Used with ecs_filter_init. */ +typedef struct ecs_filter_desc_t { + /* Terms of the filter. If a filter has more terms than + * ECS_TERM_CACHE_SIZE use terms_buffer */ + ecs_term_t terms[ECS_TERM_DESC_CACHE_SIZE]; + + /* For filters with lots of terms an outside array can be provided. */ + ecs_term_t *terms_buffer; + int32_t terms_buffer_count; + + /* Substitute IsA relationships by default. If true, any term with 'set' + * assigned to DefaultSet will be modified to Self|SuperSet(IsA). */ + bool substitute_default; + + /* Filter expression. Should not be set at the same time as terms array */ + const char *expr; + + /* Optional name of filter, used for debugging. If a filter is created for + * a system, the provided name should match the system name. */ + const char *name; +} ecs_filter_desc_t; + + +/** Used with ecs_query_init. */ +typedef struct ecs_query_desc_t { + /* Filter for the query */ + ecs_filter_desc_t filter; + + /* Component to be used by order_by */ + ecs_entity_t order_by_component; + + /* Callback used for ordering query results. If order_by_id is 0, the + * pointer provided to the callback will be NULL. If the callback is not + * set, results will not be ordered. */ + ecs_order_by_action_t order_by; + + /* Id to be used by group_by. This id is passed to the group_by function and + * can be used identify the part of an entity type that should be used for + * grouping. */ + ecs_id_t group_by_id; + + /* 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; + + /* Context to pass to group_by */ + void *group_by_ctx; + + /* Function to free group_by_ctx */ + ecs_ctx_free_t group_by_ctx_free; + + /* If set, the query will be created as a subquery. A subquery matches at + * most a subset of its parent query. Subqueries do not directly receive + * (table) notifications from the world. Instead parent queries forward + * results to subqueries. This can improve matching performance, as fewer + * queries need to be matched with new tables. + * Subqueries can be nested. */ + ecs_query_t *parent; + + /* INTERNAL PROPERTY - system to be associated with query. Do not set, as + * this will change in future versions. */ + ecs_entity_t system; +} ecs_query_desc_t; + + +/** Used with ecs_trigger_init. */ +typedef struct ecs_trigger_desc_t { + /* Entity to associate with trigger */ + ecs_entity_desc_t entity; + + /* Term specifying the id to subscribe for */ + ecs_term_t term; + + /* Filter expression. May only contain a single term. If this field is set, + * the term field is ignored. */ + const char *expr; + + /* Events to trigger on (OnAdd, OnRemove, OnSet, UnSet) */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + + /* Callback to invoke on an event */ + ecs_iter_action_t callback; + + /* Associate with entity */ + ecs_entity_t self; + + /* 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; +} ecs_trigger_desc_t; + + +/** Used with ecs_observer_init. */ +typedef struct ecs_observer_desc_t { + /* Entity to associate with observer */ + ecs_entity_desc_t entity; + + /* Filter for observer */ + ecs_filter_desc_t filter; + + /* Events to observe (OnAdd, OnRemove, OnSet, UnSet) */ + ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + + /* Callback to invoke on an event */ + ecs_iter_action_t callback; + + /* Associate with entity */ + ecs_entity_t self; + + /* 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; +} ecs_observer_desc_t; + +/** @} */ + + +/** + * @defgroup builtin_components Builtin components + * @{ + */ + +/** A (string) identifier. */ +typedef struct EcsIdentifier { + char *value; + ecs_size_t length; + uint64_t hash; +} EcsIdentifier; + +/** Component information. */ +typedef struct EcsComponent { + ecs_size_t size; /* Component size */ + ecs_size_t alignment; /* Component alignment */ +} EcsComponent; + +/** Component that stores an ecs_type_t. + * This component allows for the creation of entities that represent a type, and + * therefore the creation of named types. This component is typically + * instantiated by ECS_TYPE. */ +typedef struct EcsType { + ecs_type_t type; /* Preserved nested types */ + ecs_type_t normalized; /* Union of type and nested AND types */ +} EcsType; + +/** Component that contains lifecycle callbacks for a component. */ +struct EcsComponentLifecycle { + ecs_xtor_t ctor; /* ctor */ + ecs_xtor_t dtor; /* dtor */ + ecs_copy_t copy; /* copy assignment */ + ecs_move_t move; /* move assignment */ + + void *ctx; /* User defined context */ + + /* Ctor + copy */ + ecs_copy_ctor_t copy_ctor; + + /* Ctor + move */ + ecs_move_ctor_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_ctor_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_ctor_t move_dtor; + + /* 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_on_set_t on_set; +}; + +/** Component that stores reference to trigger */ +typedef struct EcsTrigger { + const ecs_trigger_t *trigger; +} EcsTrigger; + +/** Component that stores reference to observer */ +typedef struct EcsObserver { + const ecs_observer_t *observer; +} EcsObserver; + +/** Component for storing a query */ +typedef struct EcsQuery { + ecs_query_t *query; +} EcsQuery; + +/** @} */ + + +/** + * @defgroup misc_types Miscalleneous types + * @{ + */ + +/** Type that contains information about the world. */ +typedef struct ecs_world_info_t { + ecs_entity_t last_component_id; /* Last issued component entity id */ + ecs_entity_t last_id; /* Last issued entity id */ + ecs_entity_t min_id; /* First allowed entity id */ + ecs_entity_t max_id; /* Last allowed entity id */ + + FLECS_FLOAT delta_time_raw; /* Raw delta time (no time scaling) */ + FLECS_FLOAT delta_time; /* Time passed to or computed by ecs_progress */ + FLECS_FLOAT time_scale; /* Time scale applied to delta_time */ + FLECS_FLOAT target_fps; /* Target fps */ + FLECS_FLOAT frame_time_total; /* Total time spent processing a frame */ + FLECS_FLOAT system_time_total; /* Total time spent in systems */ + FLECS_FLOAT merge_time_total; /* Total time spent in merges */ + FLECS_FLOAT world_time_total; /* Time elapsed in simulation */ + FLECS_FLOAT world_time_total_raw; /* Time elapsed in simulation (no scaling) */ + + int32_t frame_count_total; /* Total number of frames */ + int32_t merge_count_total; /* Total number of merges */ + int32_t pipeline_build_count_total; /* Total number of pipeline builds */ + int32_t systems_ran_frame; /* Total number of systems ran in last frame */ +} ecs_world_info_t; + +/** @} */ + +/* Only include deprecated definitions if deprecated addon is required */ +#ifdef FLECS_DEPRECATED +#include "flecs/addons/deprecated.h" +#endif + + + +/** + * @defgroup type_roles Type Roles + * @{ + */ + +/* Type roles are used to indicate the role of an entity in a type. If no flag + * is specified, the entity is interpreted as a regular component or tag. Flags + * are added to an entity by using a bitwise OR (|). An example: + * + * ecs_entity_t parent = ecs_new(world, 0); + * ecs_entity_t child = ecs_add_pair(world, e, EcsChildOf, parent); + * + * Type flags can also be used in type expressions, without the ECS prefix: + * + * ECS_ENTITY(world, Base, Position); + * ECS_TYPE(world, InstanceOfBase, (IsA, Base)); + */ + +/** Role bit added to roles to differentiate between roles and generations */ +#define ECS_ROLE (1ull << 63) + +/** Cases are used to switch between mutually exclusive components */ +FLECS_API extern const ecs_id_t ECS_CASE; + +/** Switches allow for fast switching between mutually exclusive components */ +FLECS_API extern const ecs_id_t ECS_SWITCH; + +/** The PAIR role indicates that the entity is a pair identifier. */ +FLECS_API extern const ecs_id_t ECS_PAIR; + +/** Enforce ownership of a component */ +FLECS_API extern const ecs_id_t ECS_OWNED; + +/** Track whether component is enabled or not */ +FLECS_API extern const ecs_id_t ECS_DISABLED; + +/** @} */ + + +/** + * @defgroup builtin_tags Builtin Tags + * @{ + */ + +/** 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 ("*"), Used in expressions to indicate wildcard matching */ +FLECS_API extern const ecs_entity_t EcsWildcard; + +/* This entity (".", "This"). Used in expressions to indicate This entity */ +FLECS_API extern const ecs_entity_t EcsThis; + +/* Can be added to relation to indicate it is transitive. */ +FLECS_API extern const ecs_entity_t EcsTransitive; + +/* Can be added to component/relation to indicate it is final. Final components/ + * relations cannot be derived from using an IsA relationship. Queries will not + * attempt to substitute a component/relationship with IsA subsets if they are + * final. */ +FLECS_API extern const ecs_entity_t EcsFinal; + +/* Can be added to relation to indicate that it should never hold data, even + * when it or the relation object is a component. */ +FLECS_API extern const ecs_entity_t EcsTag; + +/* Tag to indicate name identifier */ +FLECS_API extern const ecs_entity_t EcsName; + +/* Tag to indicate symbol identifier */ +FLECS_API extern const ecs_entity_t EcsSymbol; + +/* Used to express parent-child relations. */ +FLECS_API extern const ecs_entity_t EcsChildOf; + +/* Used to express is-a relations. An IsA relation indicates that the subject is + * a subset of the relation object. For example: + * ecs_add_pair(world, Freighter, EcsIsA, SpaceShip); + * + * Here the Freighter is considered a subset of SpaceShip, meaning that every + * entity that has Freighter also implicitly has SpaceShip. + * + * The subject of the relation (Freighter) inherits all components from any IsA + * object (SpaceShip). If SpaceShip has a component "MaxSpeed", this component + * will also appear on Freighter after adding (IsA, SpaceShip) to Freighter. + * + * The IsA relation is transitive. This means that if SpaceShip IsA Machine, + * then Freigther is also a Machine. As a result, Freighter also inherits all + * components from Machine, just as it does from SpaceShip. + * + * Queries/filters may implicitly substitute predicates, subjects and objects + * with their IsA super/subsets. This behavior can be controlled by the "set" + * member of a query term. + */ +FLECS_API extern const ecs_entity_t EcsIsA; + +/* Tag added to module entities */ +FLECS_API extern const ecs_entity_t EcsModule; + +/* Tag added to prefab entities. Any entity with this tag is automatically + * ignored by filters/queries, unless EcsPrefab is explicitly added. */ +FLECS_API extern const ecs_entity_t EcsPrefab; + +/* When this tag is added to an entity it is skipped by all queries/filters */ +FLECS_API extern const ecs_entity_t EcsDisabled; + +/* Tag added to builtin/framework entites. This tag can be used to automatically + * hide components/systems that are part of infrastructure code vs. application + * code. The tag has no functional implications. */ +FLECS_API extern const ecs_entity_t EcsHidden; + +/* Event. Triggers when an id (component, tag, pair) is added to an entity */ +FLECS_API extern const ecs_entity_t EcsOnAdd; + +/* Event. Triggers when an id (component, tag, pair) is removed from an entity */ +FLECS_API extern const ecs_entity_t EcsOnRemove; + +/* Event. Triggers when a component is set for an entity */ +FLECS_API extern const ecs_entity_t EcsOnSet; + +/* Event. Triggers when a component is unset for an entity */ +FLECS_API extern const ecs_entity_t EcsUnSet; + +/* Event. Triggers when an entity is deleted. + * Also used as relation for defining cleanup behavior, see: + * https://github.com/SanderMertens/flecs/blob/master/docs/Relations.md#relation-cleanup-properties + */ +FLECS_API extern const ecs_entity_t EcsOnDelete; + +/* Event. Triggers when a table is created. */ +// FLECS_API extern const ecs_entity_t EcsOnCreateTable; + +/* Event. Triggers when a table is deleted. */ +// FLECS_API extern const ecs_entity_t EcsOnDeleteTable; + +/* Event. Triggers when a table becomes empty (doesn't trigger on creation). */ +// FLECS_API extern const ecs_entity_t EcsOnTableEmpty; + +/* Event. Triggers when a table becomes non-empty. */ +// FLECS_API extern const ecs_entity_t EcsOnTableNonEmpty; + +/* Event. Triggers when a trigger is created. */ +// FLECS_API extern const ecs_entity_t EcsOnCreateTrigger; + +/* Event. Triggers when a trigger is deleted. */ +// FLECS_API extern const ecs_entity_t EcsOnDeleteTrigger; + +/* Event. Triggers when observable is deleted. */ +// FLECS_API extern const ecs_entity_t EcsOnDeleteObservable; + +/* Event. Triggers when lifecycle methods for a component are registered */ +// FLECS_API extern const ecs_entity_t EcsOnComponentLifecycle; + +/* Relationship used to define what should happen when an entity is deleted that + * is added to other entities. For details see: + * https://github.com/SanderMertens/flecs/blob/master/docs/Relations.md#relation-cleanup-properties + */ +FLECS_API extern const ecs_entity_t EcsOnDeleteObject; + +/* Specifies that a component/relation/object of relation should be removed when + * it is deleted. Must be combined with EcsOnDelete or EcsOnDeleteObject. */ +FLECS_API extern const ecs_entity_t EcsRemove; + +/* Specifies that entities with a component/relation/object of relation should + * be deleted when the component/relation/object of relation is deleted. Must be + * combined with EcsOnDelete or EcsOnDeleteObject. */ +FLECS_API extern const ecs_entity_t EcsDelete; + +/* Specifies that whenever a component/relation/object of relation is deleted an + * error should be thrown. Must be combined with EcsOnDelete or + * EcsOnDeleteObject. */ +FLECS_API extern const ecs_entity_t EcsThrow; + +/* System module tags */ +FLECS_API extern const ecs_entity_t EcsOnDemand; +FLECS_API extern const ecs_entity_t EcsMonitor; +FLECS_API extern const ecs_entity_t EcsDisabledIntern; +FLECS_API extern const ecs_entity_t EcsInactive; + +/* Pipeline module tags */ +FLECS_API extern const ecs_entity_t EcsPipeline; +FLECS_API extern const ecs_entity_t EcsPreFrame; +FLECS_API extern const ecs_entity_t EcsOnLoad; +FLECS_API extern const ecs_entity_t EcsPostLoad; +FLECS_API extern const ecs_entity_t EcsPreUpdate; +FLECS_API extern const ecs_entity_t EcsOnUpdate; +FLECS_API extern const ecs_entity_t EcsOnValidate; +FLECS_API extern const ecs_entity_t EcsPostUpdate; +FLECS_API extern const ecs_entity_t EcsPreStore; +FLECS_API extern const ecs_entity_t EcsOnStore; +FLECS_API extern const ecs_entity_t EcsPostFrame; + +/* Value used to quickly check if component is builtin. This is used to quickly + * filter out tables with builtin components (for example for ecs_delete) */ +#define EcsLastInternalComponentId (ecs_id(EcsSystem)) + +/* The first user-defined component starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserComponentId (32) + +/* The first user-defined entity starts from this id. Ids up to this number + * are reserved for builtin components */ +#define EcsFirstUserEntityId (ECS_HI_COMPONENT_ID + 128) + +/** @} */ + + +/** + * @defgroup convenience_macros Convenience Macro's + * @{ + */ + +/* Macro's rely on variadic arguments which are C99 and above */ +#ifndef FLECS_LEGACY + +/** Declare a component. + * Example: + * ECS_COMPONENT(world, Position); + */ +#ifndef ECS_COMPONENT +#define ECS_COMPONENT(world, id) \ + ecs_id_t ecs_id(id) = ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + (void)ecs_id(id); +#endif + +/** Declare an extern component variable. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DECLARE. + * + * Example: + * ECS_COMPONENT_EXTERN(Position); + */ +#ifndef ECS_COMPONENT_EXTERN +#define ECS_COMPONENT_EXTERN(id)\ + extern ecs_id_t ecs_id(id); +#endif + +/** Declare a component variable outside the scope of a function. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DEFINE. + * + * Example: + * ECS_COMPONENT_IMPL(Position); + */ +#ifndef ECS_COMPONENT_DECLARE +#define ECS_COMPONENT_DECLARE(id)\ + ecs_id_t ecs_id(id); +#endif + +/** Define a component, store in variable outside of the current scope. + * Use this macro in a header when defining a component identifier globally. + * Must be used together with ECS_COMPONENT_DECLARE. + * + * Example: + * ECS_COMPONENT_DEFINE(world, Position); + */ +#ifndef ECS_COMPONENT_DEFINE +#define ECS_COMPONENT_DEFINE(world, id)\ + ecs_id(id)= ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + }); +#endif + +/** Declare a tag. + * Example: + * ECS_TAG(world, MyTag); + */ +#ifndef ECS_TAG +#define ECS_TAG(world, id)\ + ECS_ENTITY(world, id, 0); +#endif + +/** Declare an extern tag variable. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DECLARE. + * + * Example: + * ECS_TAG_EXTERN(Enemy); + */ +#ifndef ECS_TAG_EXTERN +#define ECS_TAG_EXTERN(id)\ + extern ecs_entity_t id; +#endif + +/** Declare a tag variable outside the scope of a function. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DEFINE. + * + * Example: + * ECS_TAG_DECLARE(Enemy); + */ +#ifndef ECS_TAG_DECLARE +#define ECS_TAG_DECLARE(id)\ + ecs_entity_t id; +#endif + +/** Define a tag, store in variable outside of the current scope. + * Use this macro in a header when defining a tag identifier globally. + * Must be used together with ECS_TAG_DECLARE. + * + * Example: + * ECS_TAG_DEFINE(world, Enemy); + */ +#ifndef ECS_TAG_DEFINE +#define ECS_TAG_DEFINE(world, id)\ + id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id\ + }); +#endif + +/** Declare a constructor. + * Example: + * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); + */ +#define ECS_CTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) + +/** Declare a destructor. + * Example: + * ECS_DTOR(MyType, ptr, { free(ptr->value); }); + */ +#define ECS_DTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) + +/** Declare a copy action. + * Example: + * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + */ +#define ECS_COPY(type, dst_var, src_var, ...)\ + ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare a move action. + * Example: + * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + */ +#define ECS_MOVE(type, dst_var, src_var, ...)\ + ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare an on_set action. + * Example: + * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); + */ +#define ECS_ON_SET(type, ptr, ...)\ + ECS_ON_SET_IMPL(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 + +#endif /* FLECS_LEGACY */ + +/** @} */ + +/** + * @defgroup world_api World API + * @{ + */ + +/** Create a new world. + * A world manages all the ECS data and supporting infrastructure. Applications + * must have at least one world. Entities, component and system handles are + * local to a world and should not be shared between worlds. + * + * This operation creates a world with all builtin modules loaded. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_init(void); + +/** Same as ecs_init, but with minimal set of modules loaded. + * + * @return A new world object + */ +FLECS_API +ecs_world_t* ecs_mini(void); + +/** Create a new world with arguments. + * Same as ecs_init, but allows passing in command line arguments. These can be + * used to dynamically enable flecs features to an application. Currently these + * arguments are not used. + * + * @return A new world object + */ +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); + +/** 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); + +/** 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 signaled. + * + * @param world The world. + */ +FLECS_API +bool ecs_should_quit( + const ecs_world_t *world); + +/** Register ctor, dtor, copy & move actions for component. + * + * @param world The world. + * @param component The component id for which to register the actions + * @param actions Type that contains the component actions. + */ +FLECS_API +void ecs_set_component_actions_w_id( + ecs_world_t *world, + ecs_id_t id, + EcsComponentLifecycle *actions); + +#ifndef FLECS_LEGACY +#define ecs_set_component_actions(world, component, ...)\ + ecs_set_component_actions_w_id(world, ecs_id(component), &(EcsComponentLifecycle)__VA_ARGS__) +#endif + +/** Set a world context. + * This operation allows an application to register custom data with a world + * that can be accessed anywhere where the application has the world object. + * + * @param world The world. + * @param ctx A pointer to a user defined structure. + */ +FLECS_API +void ecs_set_context( + ecs_world_t *world, + void *ctx); + +/** Get the world context. + * This operation retrieves a previously set world context. + * + * @param world The world. + * @return The context set with ecs_set_context. If no context was set, the + * function returns NULL. + */ +FLECS_API +void* ecs_get_context( + const ecs_world_t *world); + +/** Get world info. + * + * @param world The world. + * @return Pointer to the world info. This pointer will remain valid for as long + * as the world is valid. + */ +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. Note that this function does not allocate + * memory for components (use ecs_dim_type for that). + * + * @param world The world. + * @param entity_count The number of entities to preallocate. + */ +FLECS_API +void ecs_dim( + ecs_world_t *world, + int32_t entity_count); + +/** Set a range for issueing new entity ids. + * This function constrains the entity identifiers returned by ecs_new to the + * specified range. This operation can be used to ensure that multiple processes + * can run in the same simulation without requiring a central service that + * coordinates issueing identifiers. + * + * If id_end is set to 0, the range is infinite. If id_end is set to a non-zero + * value, it has to be larger than id_start. If id_end is set and ecs_new is + * invoked after an id is issued that is equal to id_end, the application will + * abort. + * + * @param world The world. + * @param id_start The start of the range. + * @param id_end The end of the range. + */ +FLECS_API +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end); + +/** 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); + +/** Enable world locking while in progress. + * When locking is enabled, Flecs will lock the world while in progress. This + * allows applications to interact with the world from other threads without + * running into race conditions. + * + * This is a better alternative to applications putting a lock around calls to + * ecs_progress, since ecs_progress can sleep when FPS control is enabled, + * which is time during which other threads could perform work. + * + * Locking must be enabled before applications can use the ecs_lock and + * ecs_unlock functions. Locking is turned off by default. + * + * @param world The world. + * @param enable True if locking is to be enabled. + * @result The previous value of the setting. + */ +FLECS_API +bool ecs_enable_locking( + ecs_world_t *world, + bool enable); + +/** Locks the world. + * See ecs_enable_locking for details. + * + * @param world The world. + */ +FLECS_API +void ecs_lock( + ecs_world_t *world); + +/** Unlocks the world. + * See ecs_enable_locking for details. + * + * @param world The world. + */ +FLECS_API +void ecs_unlock( + ecs_world_t *world); + +/** Wait until world becomes available. + * When a non-flecs thread needs to interact with the world, it should invoke + * this function to wait until the world becomes available (as in, it is not + * progressing the frame). Invoking this function guarantees that the thread + * will not starve. (as opposed to simply taking the world lock). + * + * An application will have to invoke ecs_end_wait after this function returns. + * + * @param world The world. + */ +FLECS_API +void ecs_begin_wait( + ecs_world_t *world); + +/** Release world after calling ecs_begin_wait. + * This operation should be invoked after invoking ecs_begin_wait, and will + * release the world back to the thread running the main loop. + * + * @param world The world. + */ +FLECS_API +void ecs_end_wait( + ecs_world_t *world); + +/** Enable or disable tracing. + * This will enable builtin tracing. For tracing to work, it will have to be + * compiled in which requires defining one of the following macro's: + * + * ECS_TRACE_0 - All tracing is disabled + * ECS_TRACE_1 - Enable tracing level 1 + * ECS_TRACE_2 - Enable tracing level 2 and below + * ECS_TRACE_3 - Enable tracing level 3 and below + * + * If no tracing level is defined and this is a debug build, ECS_TRACE_3 will + * have been automatically defined. + * + * The provided level corresponds with the tracing level. If -1 is provided as + * value, warnings are disabled. If -2 is provided, errors are disabled as well. + * + * @param level Desired tracing level. + */ +FLECS_API +void ecs_tracing_enable( + int level); + +/** Enable/disable tracing with colors. + * By default colors are enabled. + * + * @param enabled Whether to enable tracing with colors. + */ +FLECS_API +void ecs_tracing_color_enable( + bool enabled); + +/** 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, + FLECS_FLOAT fps); + +/** Get current number of threads. */ +FLECS_API +int32_t ecs_get_threads( + ecs_world_t *world); + +/** @} */ + +/** + * @defgroup creating_entities Creating Entities + * @{ + */ + +/** Create new entity id. + * This operation returns an unused entity id. + * + * @param world The world. + * @return The new entity id. + */ +FLECS_API +ecs_entity_t ecs_new_id( + ecs_world_t *world); + +/** Create new component id. + * This operation returns a new component id. Component ids are the same as + * entity ids, but can make use of the [1 .. ECS_HI_COMPONENT_ID] range. + * + * This operation does not recycle ids. + * + * @param world The world. + * @return The new component id. + */ +FLECS_API +ecs_entity_t ecs_new_component_id( + ecs_world_t *world); + +/** Create new entity. + * This operation creates a new entity with a single entity in its type. The + * entity may contain type roles. This operation recycles ids. + * + * @param world The world. + * @param entity The entity 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 a new entity. + * This operation creates a new entity with a single component in its type. This + * operation accepts variables created with ECS_COMPONENT, ECS_TYPE and ECS_TAG. + * This operation recycles ids. + * + * @param world The world. + * @param component The component. + * @return The new entity. + */ +#ifndef ecs_new +#define ecs_new(world, type) ecs_new_w_id(world, ecs_id(type)) +#endif + +/** 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); + +/** 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); + +/** Create a new type entity. + * This operation creates a new type entity, or finds an existing one. The find + * or create behavior is the same as ecs_entity_init. + * + * A type entity is an entity with the EcsType component. This component + * a pointer to an ecs_type_t, which allows for the creation of named types. + * Named types are used in a few places, such as for pipelines and filter terms + * with the EcsAndFrom or EcsOrFrom operators. + * + * When an existing type entity is found, its types are verified with the + * provided values. If the values do not match, the operation will fail. + * + * See the documentation of ecs_type_desc_t for more details. + * + * @param world The world. + * @param desc Type entity init parameters. + * @return A handle to the new or existing type, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_type_init( + ecs_world_t *world, + const ecs_type_desc_t *desc); + +/** Create N new entities. + * This operation is the same as ecs_new_w_id, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param entity The entity. + * @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); + +/** Create N new entities and initialize components. + * This operation is the same as ecs_bulk_new_w_type, but initializes components + * with the provided component array. Instead of a type the operation accepts an + * array of component identifiers (entities). The component arrays need to be + * provided in the same order as the component identifiers. + * + * @param world The world. + * @param components Array with component identifiers. + * @param count The number of entities to create. + * @param data The data arrays to initialize the components with. + * @return The first entity id of the newly created entities. + */ +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_data( + ecs_world_t *world, + int32_t count, + const ecs_ids_t *component_ids, + void *data); + +/** Create N new entities. + * This operation is the same as ecs_new, but creates N entities + * instead of one and does not recycle ids. + * + * @param world The world. + * @param component The component type. + * @param count The number of entities to create. + * @return The first entity id of the newly created entities. + */ +#ifndef ecs_bulk_new +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_id(world, ecs_id(component), count) +#endif + +/** 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. + * + * @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); + +/** @} */ + +/** + * @defgroup adding_removing Adding & Removing + * @{ + */ + +/** Add an entity to an entity. + * This operation adds a single entity to the type of an entity. Type roles may + * be used in combination with the added entity. If the entity already has the + * entity, this operation will have no side effects. + * + * @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); + +/** Add a component, type or tag to an entity. + * This operation adds a type to an entity. The resulting type of the entity + * will be the union of the previous type and the provided type. If the added + * type did not have new components, this operation will have no side effects. + * + * This operation accepts variables declared by ECS_COMPONENT, ECS_TYPE and + * ECS_TAG. + * + * @param world The world. + * @param entity The entity. + * @param component The component, type or tag to add. + */ +#ifndef ecs_add +#define ecs_add(world, entity, component)\ + ecs_add_id(world, entity, ecs_id(component)) +#endif + +/** Remove an entity from an entity. + * This operation removes a single entity from the type of an entity. Type roles + * may be used in combination with the added entity. If the entity does not have + * the entity, this operation will have no side effects. + * + * @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); + +/** Remove a component, type or tag from an entity. + * This operation removes a type to an entity. The resulting type of the entity + * will be the difference of the previous type and the provided type. If the + * type did not overlap with the entity type, this operation has no side effects. + * + * This operation accepts variables declared by ECS_COMPONENT, ECS_TYPE and + * ECS_TAG. + * + * @param world The world. + * @param entity The entity. + * @param component The component, type or tag to remove. + */ +#ifndef ecs_remove +#define ecs_remove(world, entity, type)\ + ecs_remove_id(world, entity, ecs_id(type)) +#endif + +/** @} */ + + +/** + * @defgroup enabling_disabling Enabling & Disabling components. + * @{ + */ + +/** 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_component_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable); + +#define ecs_enable_component(world, entity, T, enable)\ + ecs_enable_component_w_id(world, entity, ecs_id(T), 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_component_enabled_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +#define ecs_is_component_enabled(world, entity, T)\ + ecs_is_component_enabled_w_id(world, entity, ecs_id(T)) + +/** @} */ + + +/** + * @defgroup pairs Pairs + * @{ + */ + +/** Make a pair identifier. + * This function is equivalent to using the ecs_pair macro, and is added for + * convenience to make it easier for non C/C++ bindings to work with pairs. + * + * @param relation The relation of the pair. + * @param object The object of the pair. + */ +FLECS_API +ecs_id_t ecs_make_pair( + ecs_entity_t relation, + ecs_entity_t object); + +/** This operation accepts regular entities. For passing in component identifiers + * use ecs_typeid, like this: + * + * ecs_new_w_pair(world, ecs_id(relation), object) + * + * @param world The world. + * @param relation The relation part of the pair to add. + * @param object The object part of the pair to add. + * @return The new entity. + */ +#define ecs_new_w_pair(world, relation, object)\ + ecs_new_w_id(world, ecs_pair(relation, object)) + +/** Add a pair. + * This operation adds a pair to an entity. A pair is a combination of a + * relation and an object, can can be used to store relationships between + * entities. Example: + * + * subject = Alice, relation = Likes, object = Bob + * + * This operation accepts regular entities. For passing in component identifiers + * use ecs_typeid, like this: + * + * ecs_add_pair(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity to which to add the pair. + * @param relation The relation part of the pair to add. + * @param object The object part of the pair to add. + */ +#define ecs_add_pair(world, subject, relation, object)\ + ecs_add_id(world, subject, ecs_pair(relation, object)) + +/** Remove a pair. + * This operation removes a pair from an entity. A pair is a combination of a + * relation and an object, can can be used to store relationships between + * entities. Example: + * + * subject = Alice, relation = Likes, object = Bob + * + * This operation accepts regular entities. For passing in component identifiers + * use ecs_typeid, like this: + * + * ecs_remove_pair(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity from which to remove the pair. + * @param relation The relation part of the pair to remove. + * @param object The object part of the pair to remove. + */ +#define ecs_remove_pair(world, subject, relation, object)\ + ecs_remove_id(world, subject, ecs_pair(relation, object)) + +/** Test for a pair. + * This operation tests if an entity has a pair. This operation accepts regular + * entities. For passing in component identifiers use ecs_typeid, like this: + * + * ecs_has_pair(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity from which to remove the pair. + * @param relation The relation part of the pair to remove. + * @param object The object part of the pair to remove. + */ +#define ecs_has_pair(world, subject, relation, object)\ + ecs_has_id(world, subject, ecs_pair(relation, object)) + + +#ifndef FLECS_LEGACY + +/** Set relation of pair. + * This operation sets data for a pair, where the relation determines the type. + * A pair is a combination of a relation and an object, can can be used to store + * relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the relation is not a component, + * it will fail. The object part of the pair expects a regular entity. To pass + * a component as object, use ecs_typeid like this: + * + * ecs_set_pair(world, subject, relation, ecs_id(object)) + * + * @param world The world. + * @param subject The entity on which to set the pair. + * @param relation The relation part of the pair. This must be a component. + * @param object The object part of the pair. + */ +#define ecs_set_pair(world, subject, relation, object, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(ecs_id(relation), object),\ + sizeof(relation), &(relation)__VA_ARGS__) + + +/** Set object of pair. + * This operation sets data for a pair, where the object determines the type. + * A pair is a combination of a relation and an object, can can be used to store + * relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the object is not a component, + * it will fail. The relation part of the pair expects a regular entity. To pass + * a component as relation, use ecs_typeid like this: + * + * ecs_set_pair_object(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity. + * @param relation The relation part of the pair. + * @param object The object part of the pair. This must be a component. + */ +#define ecs_set_pair_object(world, subject, relation, object, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(relation, ecs_id(object)),\ + sizeof(object), &(object)__VA_ARGS__) + +#define ecs_get_mut_pair(world, subject, relation, object, is_added)\ + (ECS_CAST(relation*, ecs_get_mut_id(world, subject,\ + ecs_pair(ecs_id(relation), object), is_added))) + +#define ecs_get_mut_pair_object(world, subject, relation, object, is_added)\ + (ECS_CAST(object*, ecs_get_mut_id(world, subject,\ + ecs_pair(relation, ecs_id(object)), is_added))) + +#define ecs_modified_pair(world, subject, relation, object)\ + ecs_modified_id(world, subject, ecs_pair(relation, object)) + +#endif + +/** Get relation of pair. + * This operation obtains the value of a pair, where the relation determines the + * type. A pair is a combination of a relation and an object, can can be used to + * store relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the relation is not a component, + * it will fail. The object part of the pair expects a regular entity. To pass + * a component as relation, use ecs_typeid like this: + * + * ecs_get_pair(world, subject, relation, ecs_id(object)) + * + * @param world The world. + * @param subject The entity. + * @param relation The relation part of the pair. Must be a component. + * @param object The object part of the pair. + */ +#define ecs_get_pair(world, subject, relation, object)\ + (ECS_CAST(relation*, ecs_get_id(world, subject,\ + ecs_pair(ecs_id(relation), object)))) + +/** Get object of pair. + * This operation obtains the value of a pair, where the object determines the + * type. A pair is a combination of a relation and an object, can can be used to + * store relationships between entities. + * + * Pairs can contain data if either the relation or object of the pair are a + * component. If both are a component, the relation takes precedence. + * + * If this operation is used with a pair where the object is not a component, + * it will fail. The relation part of the pair expects a regular entity. To pass + * a component as relation, use ecs_typeid like this: + * + * ecs_get_pair_object(world, subject, ecs_id(relation), object) + * + * @param world The world. + * @param subject The entity. + * @param relation The relation part of the pair. Must be a component. + * @param object The object part of the pair. + */ +#define ecs_get_pair_object(world, subject, relation, object)\ + (ECS_CAST(object*, ecs_get_id(world, subject,\ + ecs_pair(relation, ecs_id(object))))) + +/** @} */ + + +/** + * @defgroup deleting Deleting Entities and components + * @{ + */ + +/** Clear all components. + * This operation will clear all components from an entity but will not delete + * the entity itself. This effectively prevents the entity id from being + * recycled. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity); + +/** Delete an entity. + * This operation will delete an entity and all of its components. The entity id + * will be recycled. Repeatedly calling ecs_delete without ecs_new, + * ecs_new_w_id or ecs_new_w_type will cause a memory leak as it will cause + * the list with ids that can be recycled to grow unbounded. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity); + + +/** Delete children of an entity. + * This operation deletes all children of a parent entity. If a parent has no + * children this operation has no effect. + * + * @param world The world. + * @param parent The parent entity. + */ +FLECS_API +void ecs_delete_children( + ecs_world_t *world, + ecs_entity_t parent); + +/** @} */ + + +/** + * @defgroup getting Getting 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. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + + +/** Get an immutable pointer to a component. + * Same as ecs_get_id, but accepts the typename of a component. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +#define ecs_get(world, entity, component)\ + (ECS_CAST(const component*, ecs_get_id(world, entity, ecs_id(component)))) + +/* -- Get cached pointer -- */ + +/** Get an immutable reference to a component. + * This operation is similar to ecs_get_id but it stores temporary + * information in a `ecs_ref_t` value which allows subsequent lookups to be + * faster. + * + * @param world The world. + * @param ref Pointer to a ecs_ref_t value. Must be initialized. + * @param entity The entity. + * @param component The entity id of the component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +FLECS_API +const void* ecs_get_ref_w_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_id_t id); + +/** Get an immutable reference to a component. + * Same as ecs_get_ref_w_id, but accepts the typename of a component. + * + * @param world The world. + * @param ref Pointer to a ecs_ref_t value. Must be initialized. + * @param entity The entity. + * @param id The component to obtain. + * @return The component pointer, NULL if the entity does not have the component. + */ +#define ecs_get_ref(world, ref, entity, component)\ + (ECS_CAST(const component*, ecs_get_ref_w_id(world, ref, entity, ecs_id(component)))) + +/** Get case for switch. + * This operation gets the current case for the specified switch. If the current + * switch is not set for the entity, the operation will return 0. + * + * @param world The world. + * @param e The entity. + * @param sw The switch for which to obtain the case. + * @return The current case for the specified switch. + */ +FLECS_API +ecs_entity_t ecs_get_case( + const ecs_world_t *world, + ecs_entity_t e, + ecs_entity_t sw); + +/** @} */ + + +/** + * @defgroup setting Setting Components + * @{ + */ + +/** Get a mutable pointer to a component. + * This operation is similar to ecs_get_id but it returns a mutable + * pointer. If this operation is invoked from inside a system, the entity will + * be staged and a pointer to the staged component will be returned. + * + * If the entity did not yet have the component, the component will be added by + * this operation. In this case the is_added out parameter will be set to true. + * + * @param world The world. + * @param entity The entity. + * @param id The entity id of the component to obtain. + * @param is_added Out parameter that returns true if the component was added. + * @return The component pointer. + */ +FLECS_API +void* ecs_get_mut_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added); + +/** Get a mutable pointer to a component. + * Same as ecs_get_mut_id but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param T The component type to obtain. + * @param is_added Out parameter that returns true if the component was added. + * @return The component pointer. + */ +#define ecs_get_mut(world, entity, T, is_added)\ + (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T), is_added))) + +/** Emplace a component. + * Emplace is similar to get_mut except that the component constructor is not + * invoked for the returned pointer, allowing the component to be "constructed" + * directly in the storage. + * + * Emplace can only be used if the entity does not yet have the component. If + * the entity has the component, the operation will fail. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The (uninitialized) component pointer. + */ +FLECS_API +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Emplace a component. + * Same as ecs_emplace_id but accepts a typename. + * + * @param world The world. + * @param entity The entity. + * @param id The component to obtain. + * @return The (uninitialized) component pointer. + */ +#define ecs_emplace(world, entity, T)\ + (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) + + +/** Signal that a component has been modified. + * This operation allows an application to signal to Flecs that a component has + * been modified. As a result, OnSet systems will be invoked. + * + * This operation is commonly used together with ecs_get_mut. + * + * @param world The world. + * @param entity The entity. + * @param component The entity 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); + +/** Signal that a component has been modified. + * Same as ecs_modified_id but accepts a component typename. + * + * @param world The world. + * @param entity The entity. + * @param id The component that was modified. + */ +#define ecs_modified(world, entity, component)\ + ecs_modified_id(world, entity, ecs_id(component)) + +/** Set the value of a component. + * This operation allows an application to set the value of a component. The + * operation is equivalent to calling ecs_get_mut and ecs_modified. + * + * If the provided entity is 0, a new entity will be created. + * + * @param world The world. + * @param entity The entity. + * @param component The entity id of the component to set. + * @param size The size of the pointer to the value. + * @param ptr The pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +FLECS_API +ecs_entity_t ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr); + +/** Set the value of a component. + * Same as ecs_set_id, but accepts a component typename and + * automatically determines the type size. + * + * @param world The world. + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +#define ecs_set_ptr(world, entity, component, ptr)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) + +/* Conditionally skip macro's as compound literals and variadic arguments are + * not supported in C89 */ +#ifndef FLECS_LEGACY + +/** Set the value of a component. + * Same as ecs_set_ptr, but accepts a value instead of a pointer to a value. + * + * @param world The world. + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointer to the value. + * @return The entity. A new entity if no entity was provided. + */ +#define ecs_set(world, entity, component, ...)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) + +#endif + +/** @} */ + + +/** + * @defgroup singleton Singleton components + * @{ + */ + +#define ecs_singleton_get(world, comp)\ + ecs_get(world, ecs_id(comp), comp) + +#ifndef FLECS_LEGACY +#define ecs_singleton_set(world, comp, ...)\ + ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) +#endif + +#define ecs_singleton_get_mut(world, comp)\ + ecs_get_mut(world, ecs_id(comp), comp, NULL) + +#define ecs_singleton_modified(world, comp)\ + ecs_modified(world, ecs_id(comp), comp) + +/** + * @defgroup testing Testing Components + * @{ + */ + +/** Test if an entity has an entity. + * This operation returns true if the entity has the provided entity in its + * type. + * + * @param world The world. + * @param entity The entity. + * @param id The id to test for. + * @return True if the entity has the entity, false if not. + */ +FLECS_API +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +/** Test if an entity has a component, type or tag. + * This operation returns true if the entity has the provided component, type or + * tag in its type. + * + * @param world The world. + * @param entity The entity. + * @param type The component, type or tag to test for. + * @return True if the entity has the type, false if not. + */ +#ifndef ecs_has +#define ecs_has(world, entity, type)\ + ecs_has_id(world, entity, ecs_id(type)) +#endif + +/** Test if an entity owns an entity. + * This operation is similar to ecs_has, but will return false if the entity + * does not own the entity, which is the case if the entity is defined on + * a base entity with an IsA pair. + * + * @param world The world. + * @param entity The entity. + * @param type The entity to test for. + * @return True if the entity owns the entity, false if not. + */ +#ifndef ecs_owns +#define ecs_owns(world, entity, has, owned)\ + ecs_type_has_id(world, ecs_get_type(world, entity), has, owned) +#endif + +/** @} */ + +/** + * @defgroup metadata Entity Metadata + * @{ + */ + +/** Test whether an entity is valid. + * Entities that are valid can be used with API functions. + * + * An entity is valid if it is not 0 and if it is alive. If the provided id has + * a role or a pair, the contents of the role or the pair will be checked for + * validity. + * + * is_valid will return true for ids that don't exist (alive or not alive). This + * allows for using ids that have never been created by ecs_new or similar. In + * this the function differs from ecs_is_alive, which will return false for + * entities that do not yet exist. + * + * The 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. + * An entity is alive when it has been returned by ecs_new (or similar) or if + * it is not empty (componentts have been explicitly added to the id). + * + * @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); + +/** 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 relation, the parent + * (object part of the pair) will have been stored in a 32 bit value, which + * cannot store the entity generation. This function can retrieve the identifier + * with the current generation for that id. + * + * 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 (such as a global constant or an id from a remote application). + * + * Before this operation the id must either not yet exist, or must exist with + * the same generation as the provided id. If the id has been recycled and the + * provided id does not have the same generation count, the function will fail. + * + * If the provided entity is not alive, and the provided generation count is + * equal to the current generation (which is the future generation when the id + * will be recycled) the id will become alive again. + * + * 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. + * + * This behavior ensures that an application can use ecs_ensure to track the + * lifecycle of an id without explicitly having to create it. It also protects + * against reviving an id with a generation count that was not yet due. + * + * @param world The world. + * @param entity The entity id to make alive. + */ +FLECS_API +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t e); + +/** Test whether an entity exists. + * Similar as ecs_is_alive, but ignores entity generation count. + * + * @param world The world. + * @param e 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 e); + +/** 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 +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. + */ +FLECS_API +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get the typeid of an entity. + * + * @param world The world. + * @param entity The entity. + * @return The typeid of the entity. + */ +FLECS_API +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t e); + +/** Get the name of an entity. + * This will return the name as specified in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no 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 name as specified in the EcsSymbol component. + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + */ +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 will be stored in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @param name The entity's name. + * @return The provided entity, or a new entity if 0 was provided. + */ +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 will be stored in the EcsName component. + * + * @param world The world. + * @param entity The entity. + * @param symbol The entity's symbol. + * @return The provided entity, or a new entity if 0 was provided. + */ +FLECS_API +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *symbol); + +/** Convert type role to string. + * This operation converts a type role to a string. + * + * @param world The world. + * @param entity The entity containing the type role. + * @return The type role string, or NULL if no type role is provided. + */ +FLECS_API +const char* ecs_role_str( + ecs_entity_t entity); + +/** 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. + * @param buffer The buffer in which to store the string. + * @param buffer_len The length of the provided buffer. + * @return The number of characters required to write the string. + */ +FLECS_API +size_t ecs_id_str( + const ecs_world_t *world, + ecs_id_t entity, + char *buffer, + size_t buffer_len); + +/** Get the object of a relation. + * This will return a object of the entity for the specified relation. The index + * allows for iterating through the objects, if a single entity has multiple + * objects for the same relation. + * + * If the index is larger than the total number of instances the entity has for + * the relation, the operation will return 0. + * + * @param world The world. + * @param entity The entity. + * @param rel The relation between the entity and the object. + * @param index The index of the relation instance. + * @return The object for the relation at the specified index. + */ +FLECS_API +ecs_entity_t ecs_get_object( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index); + +/** Enable or disable an 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); + +/** 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); + +/** Count entities that have a component, type or tag. + * Returns the number of entities that have the specified component, type or tag. + * + * @param world The world. + * @param type The component, type or tag. + * @return The number of entities that have the component, type or tag. + */ +#define ecs_count(world, type)\ + ecs_count_type(world, ecs_type(type)) + +/** Count entities that match a filter. + * Returns the number of entities that match the specified filter. + * + * @param world The world. + * @param type The type. + * @return The number of entities that match the specified filter. + */ +FLECS_API +int32_t ecs_count_filter( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** @} */ + + +/** + * @defgroup lookup Lookups + * @{ + */ + +/** Lookup an entity by name. + * Returns an entity that matches the specified name. Only looks for entities in + * the current scope (root if no scope is provided). + * + * @param world The world. + * @param name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + */ +FLECS_API +ecs_entity_t ecs_lookup( + const ecs_world_t *world, + const char *name); + +/** 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 name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + */ +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. + */ +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 from a path. + * Same as ecs_lookup_path_w_sep, but with defaults for the separator and + * prefix. These defaults are used when looking up identifiers in a type or + * signature expression. + * + * @param world The world. + * @param parent The entity from which to resolve the path. + * @param path The path to resolve. + * @return The entity if found, else 0. + */ +#define ecs_lookup_path(world, parent, path)\ + ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) + +/** Lookup an entity from a full path. + * Same as ecs_lookup_path, but searches from the current scope, or root scope + * if no scope is set. + * + * @param world The world. + * @param path The path to resolve. + * @return The entity if found, else 0. + */ +#define ecs_lookup_fullpath(world, path)\ + ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true) + +/** Lookup an entity by its symbol name. + * This looks up an entity by the symbol name that was provided in EcsName. The + * operation does not take into account scoping, which means it will search all + * entities that have an EcsName. + * + * This operation can be useful to resolve, for example, a type by its C + * identifier, which does not include the Flecs namespacing. + */ +FLECS_API +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *symbol, + bool lookup_as_path); + +/* Add alias for entity to global scope */ +FLECS_API +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** @} */ + + +/** + * @defgroup paths Paths + * @{ + */ + +/** 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. + * @return The relative entity path. + */ +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); + +/** Get a path identifier for an entity. + * Same as ecs_get_path_w_sep, but with default values for the separator and + * prefix. These defaults are used throughout Flecs whenever identifiers are + * used in type or signature expressions. + * + * @param world The world. + * @param parent The entity from which to create the path. + * @param child The entity to which to create the path. + * @return The relative entity path. + */ +#define ecs_get_path(world, parent, child)\ + ecs_get_path_w_sep(world, parent, child, ".", NULL) + +/** Get a full path for an entity. + * Same as ecs_get_path, but with default values for the separator and + * prefix, and the path is created from the current scope, or root if no scope + * is provided. + * + * @param world The world. + * @param child The entity to which to create the path. + * @return The entity path. + */ +#define ecs_get_fullpath(world, child)\ + ecs_get_path_w_sep(world, 0, child, ".", NULL) + +/** 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); + +/** Find or create entity from path. + * Same as ecs_new_from_path_w_sep, but with defaults for sep and prefix. + * + * @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. + * @return The entity. + */ +#define ecs_new_from_path(world, parent, path)\ + ecs_new_from_path_w_sep(world, parent, path, ".", NULL) + +/** Find or create entity from full path. + * Same as ecs_new_from_path, but entity will be created from the current scope, + * or root scope if no scope is set. + * + * @param world The world. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_new_from_fullpath(world, path)\ + ecs_new_from_path_w_sep(world, 0, path, ".", NULL) + +/** 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); + +/** Add specified path to entity. + * Same as ecs_add_from_path_w_sep, but with defaults for sep and prefix. + * + * @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. + * @return The entity. + */ +#define ecs_add_path(world, entity, parent, path)\ + ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) + +/** Add specified path to entity. + * Same as ecs_add_from_path, but entity will be created from the current scope, + * or root scope if no scope is set. + * + * @param world The world. + * @param entity The entity to which to add the path. + * @param path The path to create the entity for. + * @return The entity. + */ +#define ecs_add_fullpath(world, entity, path)\ + ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) + +/** @} */ + + +/** + * @defgroup scopes Scopes + * @{ + */ + +/** Does entity have children. + * + * @param world The world + * @param entity The entity + * @return True if the entity has children, false if not. + */ +FLECS_API +int32_t ecs_get_child_count( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Return a scope iterator. + * A scope iterator iterates over all the child entities of the specified + * parent. + * + * @param world The world. + * @param parent The parent entity for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_scope_iter( + ecs_world_t *world, + ecs_entity_t parent); + +/** Return a filtered scope iterator. + * Same as ecs_scope_iter, but results will be filtered. + * + * @param world The world. + * @param parent The parent entity for which to iterate the children. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_scope_iter_w_filter( + ecs_world_t *world, + ecs_entity_t parent, + ecs_filter_t *filter); + +/** Progress the scope iterator. + * This operation progresses the scope iterator to the next table. The iterator + * must have been initialized with `ecs_scope_iter`. This operation must be + * invoked at least once before interpreting the contents of the iterator. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_scope_next( + ecs_iter_t *it); + +/** 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. + */ +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 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. + * @param id The id. + * @return The previous id. + */ +FLECS_API +ecs_entity_t ecs_get_with( + 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); + +/** @} */ + + +/** + * @defgroup terms Terms + * @{ + */ + +/** Iterator for a single (component) id. + * A term iterator returns all entities (tables) that match a single (component) + * id. The search for the matching set of entities (tables) is performed in + * constant time. + * + * Currently only trivial terms are supported (see ecs_term_is_trivial). Only + * the id field of the term needs to be initialized. + * + * @param world The world. + * @param term The term. + * @return The iterator. + */ +FLECS_API +ecs_iter_t ecs_term_iter( + const ecs_world_t *world, + ecs_term_t *term); + +/** Progress the term iterator. + * This operation progresses the term iterator to the next table. The + * iterator must have been initialized with `ecs_term_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_term_next( + ecs_iter_t *it); + +/** Test whether term id is set. + * + * @param id The term id. + * @return True when set, false when not set. + */ +FLECS_API +bool ecs_term_id_is_set( + const ecs_term_id_t *id); + +/** 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); + +/** Test whether a term is a trivial term. + * A trivial term is a term that only contains a type id. Trivial terms must not + * have read/write annotations, relation substitutions and subjects other than + * 'This'. Examples of trivial terms are: + * - 'Position' + * - 'Position(This)' + * - '(Likes, IceCream)' + * - 'Likes(This, IceCream)' + * + * Examples of non-trivial terms are: + * - '[in] Position' + * - 'Position(MyEntity)' + * - 'Position(self|superset)' + * + * Trivial terms are useful in expressions that should just represent a list of + * components, such as when parsing the list of components to add to an entity. + * + * The term passed to this operation must be finalized. Terms returned by the + * parser are guaranteed to be finalized. + * + * @param term The term. + * @return True if term is trivial, false if it is not. + */ +FLECS_API +bool ecs_term_is_trivial( + const ecs_term_t *term); + +/** Finalize term. + * Ensure that all fields of a term are consistent and filled out. This + * operation should be invoked before using and after assigning members to, or + * parsing a term. When a term contains unresolved identifiers, this operation + * will resolve and assign the identifiers. If the term contains any identifiers + * that cannot be resolved, the operation will fail. + * + * An application generally does not need to invoke this operation as the APIs + * that use terms (such as filters, queries and triggers) will finalize terms + * when they are created. + * + * The name and expr parameters are optional, and only used for giving more + * descriptive error messages. + * + * @param world The world. + * @param name The name of the entity that uses the term (such as a system). + * @param expr The string expression of which the term is a part. + * @param term The term to finalize. + * @return Zero if success, nonzero if an error occurred. + */ +FLECS_API +int ecs_term_finalize( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term); + +/** Copy resources of a term to another term. + * This operation copies one term to another term. If the source term contains + * allocated resources (such as identifiers), they will be duplicated so that + * no memory is shared between the terms. + * + * @param dst The term to copy to. + * @param src The term to copy from. + */ +FLECS_API +ecs_term_t ecs_term_copy( + const ecs_term_t *src); + +/** Move resources of a term to another term. + * Same as copy, but moves resources from src, if src->move is set to true. If + * src->move is not set to true, this operation will do a copy. + * + * The conditional move reduces redundant allocations in scenarios where a list + * of terms is partially created with allocated resources. + * + * @param dst The term to copy to. + * @param src The term to copy from. + */ +FLECS_API +ecs_term_t ecs_term_move( + ecs_term_t *src); + +/** Free resources of term. + * This operation frees all resources (such as identifiers) of a term. The term + * object itself is not freed. + * + * @param term The term to free. + */ +FLECS_API +void ecs_term_fini( + ecs_term_t *term); + +/** 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. + */ +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); + +/** @} */ + + +/** + * @defgroup filters Filters + * @{ + */ + +/** Initialize filter + * A filter is a lightweight object that can be used to query for entities in + * a world. Filters, as opposed to queries, do not cache results. They are + * therefore slower to iterate, but are faster to create. + * + * This operation will at minimum allocate an array to hold the filter terms in + * the returned filter struct. It may allocate additional memory if the provided + * description contains a name, expression, or if the provided array of terms + * contains strings (identifier names or term names). + * + * It is possible to create a filter without allocating any memory, by setting + * the "terms" and "term_count" members directly. When doing so an application + * should not call ecs_filter_init but ecs_filter_finalize. This will ensure + * that all fields are consistent and properly filled out. + * + * @param world The world. + * @param desc Properties for the filter to create. + * @param filter_out The filter. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_filter_init( + const ecs_world_t *world, + ecs_filter_t *filter_out, + const ecs_filter_desc_t *desc); + +/** Deinitialize filter. + * Free resources associated with filter. + * + * @param filter The filter to deinitialize. + */ +FLECS_API +void ecs_filter_fini( + ecs_filter_t *filter); + +/** Finalize filter. + * When manually assigning an array of terms to the filter struct (so not when + * using ecs_filter_init), this operation should be used to ensure that all + * terms are assigned properly and all (derived) fields have been set. + * + * When ecs_filter_init is used to create the filter, this function should not + * be called. The purpose of this operation is to support creation of filters + * without allocating memory. + * + * @param filter The filter to finalize. + * @return Zero if filter is valid, non-zero if it contains errors. + * @ + */ +FLECS_API +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *filter); + +/** Convert filter to string expression. + * Convert filter terms to a string expression. The resulting expression can be + * parsed to create the same filter. + */ +FLECS_API +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** Return a filter iterator. + * A filter iterator lets an application iterate over entities that match the + * specified filter. If NULL is provided for the filter, the iterator will + * iterate all tables in the world. + * + * @param world The world. + * @param filter The filter. + * @return An iterator that can be used with ecs_filter_next. + */ +FLECS_API +ecs_iter_t ecs_filter_iter( + const ecs_world_t *world, + const ecs_filter_t *filter); + +/** Iterate tables matched by filter. + * This operation progresses the filter iterator to the next table. The + * iterator must have been initialized with `ecs_filter_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param it The iterator + * @return True if more data is available, false if not. + */ +FLECS_API +bool ecs_filter_next( + ecs_iter_t *iter); + +/** Move resources of one filter to another. */ +FLECS_API +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src); + +/** Copy resources of one filter to another. */ +FLECS_API +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src); + +/** @} */ + +/** + * @defgroup queries Queries + * @{ + */ + +/** Create a query. + * This operation creates a query. Queries are used to iterate over entities + * that match a filter and are the fastest way to find and iterate over entities + * and their components. + * + * Queries should be created once, and reused multiple times. While iterating a + * query is a cheap operation, creating and deleting a query is expensive. The + * reason for this is that queries are "prematched", which means that a query + * stores state about which entities (or rather, tables) match with the query. + * Building up this state happens during query creation. + * + * Once a query is created, matching only happens when new tables are created. + * In most applications this is an infrequent process, since it only occurs when + * a new combination of components is introduced. While matching is expensive, + * it is importent to note that matching does not happen on a per-entity basis, + * but on a per-table basis. This means that the average time spent on matching + * per frame should rapidly approach zero over the lifetime of an application. + * + * A query provides direct access to the component arrays. When an application + * creates/deletes entities or adds/removes components, these arrays can shift + * component values around, or may grow in size. This can cause unexpected or + * undefined behavior to occur if these operations are performed while + * iterating. To prevent this from happening an application should either not + * perform these operations while iterating, or use deferred operations (see + * ecs_defer_begin and ecs_defer_end). + * + * Queries can be created and deleted dynamically. If a query was not deleted + * (using ecs_query_fini) before the world is deleted, it will be deleted + * automatically. + * + * @param world The world. + * @param desc A structure describing the query properties. + * @return The new query. + */ +FLECS_API +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc); + +/** Destroy a query. + * This operation destroys a query and its resources. If the query is used as + * the parent of subqueries, those subqueries will be orphaned and must be + * deinitialized as well. + * + * @param query The query. + */ +FLECS_API +void ecs_query_fini( + ecs_query_t *query); + +/** Get filter object of query. + * This operation obtains a pointer to the internally constructed filter object + * of the query and can be used to introspect the query terms. + * + * @param query The query. + */ +FLECS_API +const ecs_filter_t* ecs_query_get_filter( + ecs_query_t *query); + +/** Return a query iterator. + * A query iterator lets an application iterate over entities that match the + * specified query. If a sorting function is specified, the query will check + * whether a resort is required upon creating the iterator. + * + * Creating a query iterator is a cheap operation that does not allocate any + * resources. An application does not need to deinitialize or free a query + * iterator before it goes out of scope. + * + * To iterate the iterator, an application should use ecs_query_next to progress + * the iterator and test if it has data. + * + * Query iteration requires an outer and an inner loop. The outer loop uses + * ecs_query_next to test if new tables are available. The inner loop iterates + * the entities in the table, and is usually a for loop that uses iter.count to + * loop through the entities and component arrays. + * + * The two loops are necessary because of how data is stored internally. + * Entities are grouped by the components they have, in tables. A single query + * can (and often does) match with multiple tables. Because each table has its + * own set of arrays, an application has to reobtain pointers to those arrays + * for each matching table. + * + * @param query The query to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter( + ecs_query_t *query); + +/** Iterate over a query. + * This operation is similar to ecs_query_iter, but starts iterating from a + * specified offset, and will not iterate more than limit entities. + * + * @param query The query to iterate. + * @param offset The number of entities to skip. + * @param limit The maximum number of entities to iterate. + * @return The query iterator. + */ +FLECS_API +ecs_iter_t ecs_query_iter_page( + ecs_query_t *query, + int32_t offset, + int32_t limit); + +/** Progress the query iterator. + * This operation progresses the query iterator to the next table. The + * iterator must have been initialized with `ecs_query_iter`. This operation + * must be invoked at least once before interpreting the contents of the + * iterator. + * + * @param iter The iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next( + ecs_iter_t *iter); + +/** Progress the query iterator with filter. + * This operation is the same as ecs_query_next, but accepts a filter as an + * argument. Entities not matching the filter will be skipped by the iterator. + * + * @param iter The iterator. + * @param filter The filter to apply to the iterator. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_w_filter( + ecs_iter_t *iter, + const ecs_filter_t *filter); + +/** Progress the query iterator for a worker thread. + * This operation is similar to ecs_query_next, but provides the ability to + * divide entities up across multiple worker threads. The operation accepts a + * current thread id and a total thread id, which is used to determine which + * subset of entities should be assigned to the current thread. + * + * Current should be less than total, and there should be as many as total + * threads. If there are less entities in a table than there are threads, only + * as many threads as there are entities will iterate that table. + * + * @param it The iterator. + * @param stage_current Id of current stage. + * @param stage_count Total number of stages. + * @returns True if more data is available, false if not. + */ +FLECS_API +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t stage_current, + int32_t stage_count); + +/** 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 + * + * @param query The query. + * @return true if entities changed, otherwise false. + */ +FLECS_API +bool ecs_query_changed( + ecs_query_t *query); + +/** Returns whether query is orphaned. + * When the parent query of a subquery is deleted, it is left in an orphaned + * state. The only valid operation on an orphaned query is deleting it. Only + * subqueries can be orphaned. + * + * @param query The query. + * @return true if query is orphaned, otherwise false. + */ +FLECS_API +bool ecs_query_orphaned( + ecs_query_t *query); + +/** @} */ + + +/** + * @defgroup trigger Triggers + */ + +/** Create trigger. + * Triggers notify the application when certain events happen such as adding or + * removing components. + * + * An application can change the trigger callback or context pointer by calling + * ecs_trigger_init for an existing trigger entity, by setting the + * ecs_trigger_desc_t::entity.entity field in combination with callback and/or + * ctx. + * + * See the documentation for ecs_trigger_desc_t for more details. + * + * @param world The world. + * @param decs The trigger creation parameters. + */ +FLECS_API +ecs_entity_t ecs_trigger_init( + ecs_world_t *world, + const ecs_trigger_desc_t *desc); + +/** Get trigger context. + * This operation returns the context pointer set for the trigger. If + * the provided entity is not a trigger, the function will return NULL. + * + * @param world The world. + * @param trigger The trigger from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_trigger_ctx( + const ecs_world_t *world, + ecs_entity_t trigger); + +/** Same as ecs_get_trigger_ctx, but for binding ctx. + * The binding context is a context typically used to attach any language + * binding specific data that is needed when invoking a callback that is + * implemented in another language. + * + * @param world The world. + * @param trigger The trigger from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_trigger_binding_ctx( + const ecs_world_t *world, + ecs_entity_t trigger); + + +typedef enum ecs_payload_kind_t { + EcsPayloadNone, + EcsPayloadEntity, + EcsPayloadTable +} ecs_payload_kind_t; + +typedef struct ecs_event_desc_t { + ecs_entity_t event; + ecs_ids_t *ids; /* When NULL, notify for all ids in entity/table type */ + ecs_payload_kind_t payload_kind; + union { + ecs_entity_t entity; + struct { + ecs_table_t *table; + int32_t offset; + int32_t count; /* When 0 notify all entities starting from offset */ + } table; + } payload; + + void *param; /* Assigned to iter param member */ + + /* Observable for which to notify the triggers/observers. If NULL, the + * world will be used as observable. */ + ecs_object_t *observable; +} ecs_event_desc_t; + +/** Send event. + */ +FLECS_API +void ecs_emit( + ecs_world_t *world, + ecs_event_desc_t *desc); + +/** @} */ + + +/** + * @defgroup observer Observers + */ + +/** 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. + */ +FLECS_API +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc); + +FLECS_API +void* ecs_get_observer_ctx( + const ecs_world_t *world, + ecs_entity_t observer); + +FLECS_API +void* ecs_get_observer_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer); + +/** @} */ + + +/** + * @defgroup iterator Iterators + * @{ + */ + +/** Obtain data for a query term. + * This operation retrieves a pointer to an array of data that belongs to the + * term in the query. The index refers to the location of the term in the query, + * and starts counting from one. + * + * For example, the query "Position, Velocity" will return the Position array + * for index 1, and the Velocity array for index 2. + * + * When the specified term is not owned by the entity this function returns a + * pointer instead of an array. This happens when the source of a term is not + * the entity being iterated, such as a shared component (from a prefab), a + * component from a parent, or another entity. The ecs_term_is_owned operation + * can be used to test dynamically if a term is owned. + * + * The provided size must be either 0 or must match the size of the datatype + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_term_size. + * + * @param it The iterator. + * @param size The size of the returned array. + * @param index The index of the term in the query. + * @return A pointer to the data associated with the term. + */ +FLECS_API +void* ecs_term_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index); + +/** Same as ecs_term_w_size, but accepts a type instead of a size. */ +#define ecs_term(it, T, index)\ + ((T*)ecs_term_w_size(it, sizeof(T), index)) + +/** Obtain the component/pair id for a term. + * This operation retrieves the id for the specified query term. Typically this + * is the component id, but it can also be a pair id or a role annotated id, + * depending on the term. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return The id associated with te term. + */ +FLECS_API +ecs_id_t ecs_term_id( + const ecs_iter_t *it, + int32_t index); + +/** Obtain the source for a term. + * This operation retrieves the source of the specified term. A source is the + * entity from which the data is retrieved. If the term is owned by the iterated + * over entity/entities, the function will return id 0. + * + * This operation can be useful to retrieve, for example, the id of a parent + * entity when a component from a parent has been requested, or to retrieve the + * id from a prefab, in the case of a shared component. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return The source associated with te term. + */ +FLECS_API +ecs_entity_t ecs_term_source( + const ecs_iter_t *it, + int32_t index); + +/** Obtain the size for a term. + * This operation retrieves the size of the datatype for the term. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return The size of the datatype associated with te term. + */ +FLECS_API +size_t ecs_term_size( + const ecs_iter_t *it, + int32_t index); + +/** Test whether the term is readonly + * This operation returns whether this is a readonly term. Readonly terms are + * annotated with [in], or are added as a const type in the C++ API. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return Whether the term is readonly. + */ +FLECS_API +bool ecs_term_is_readonly( + const ecs_iter_t *it, + int32_t index); + +/** Test whether term is set. + * This function returns false for terms with the Not operator and for terms + * with the Optional operator if the matched entities (table) do not have the + * (component) id of the term. + * + * @param it The iterator. + * @param term The term. + * @return True if term is set, false if it is not set. + */ +FLECS_API +bool ecs_term_is_set( + const ecs_iter_t *it, + int32_t term); + +/** Test whether the term is owned + * This operation returns whether the term is owned by the currently iterated + * entity. This function will return false when the term is owned by another + * entity, such as a parent or a prefab. + * + * @param it The iterator. + * @param index The index of the term in the query. + * @return Whether the term is owned by the iterated over entity/entities. + */ +FLECS_API +bool ecs_term_is_owned( + const ecs_iter_t *it, + int32_t index); + +/** Get the type of the currently entities. + * This operation returns the type of the current iterated entity/entities. A + * type is a vector that contains all ids of the components that an entity has. + * + * @param it The iterator. + * @return The type of the currently iterated entity/entities. + */ +FLECS_API +ecs_type_t ecs_iter_type( + const ecs_iter_t *it); + +/** Get the table for the current entities. + * This operation returns the table of the current iterated entities + * + * @param it The iterator. + * @return The table of the currently iterated entity/entities. + */ +FLECS_API +ecs_table_t* ecs_iter_table( + const ecs_iter_t *it); + +/** Find the column index for a given id. + * This operation finds the index of a column in the current type for the + * specified id. For example, if an entity has type Position, Velocity, and the + * application requests the id for the Velocity component, this function will + * return 1. + * + * Note that the column index returned by this function starts from 0, as + * opposed to 1 for the terms. The reason for this is that the returned index + * is equivalent to using the ecs_type_get_index function, with as type the + * value returned by ecs_iter_type. + * + * This operation can be used to request columns that are not requested by a + * query. For example, a query may request Position, Velocity, but an entity + * may also have Mass. With this function the iterator can request the data for + * Mass as well, when used in combination with ecs_iter_column. + * + * @param it The iterator. + * @return The type of the currently iterated entity/entities. + */ +FLECS_API +int32_t ecs_iter_find_column( + const ecs_iter_t *it, + ecs_id_t id); + +/** Obtain data for a column index. + * This operation can be used with the id obtained from ecs_iter_find_column to + * request data from the currently iterated over entity/entities that is not + * requested by the query. + * + * The data in the returned pointer can be accessed using the same index as + * the one used to access the arrays returned by the ecs_term function. + * + * The provided size must be either 0 or must match the size of the datatype + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_iter_column_size. + * + * Note that this function can be used together with ecs_iter_type to + * dynamically iterate all data that the matched entities have. An application + * can use the ecs_vector_count function to obtain the number of elements in a + * type. All indices from 0..ecs_vector_count(type) are valid column indices. + * + * Additionally, note that this provides unprotected access to the column data. + * An iterator cannot know or prevent accessing columns that are not queried for + * and thus applications should only use this when it can be guaranteed that + * there are no other threads reading/writing the same column data. + * + * @param it The iterator. + * @param size The size of the column. + * @param index The index of the column. + * @return The data belonging to the column. + */ +FLECS_API +void* ecs_iter_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index); + +/** Same as ecs_iter_column_w_size, but accepts a type instead of a size. */ +#define ecs_iter_column(it, T, index)\ + ((T*)ecs_iter_column_w_size(it, sizeof(T), index)) + +/** Obtain size for a column index. + * This operation obtains the size for a column. The size is equal to the size + * of the datatype associated with the column. + * + * @param it The iterator. + * @param index The index of the column. + * @return The size belonging to the column. + */ +FLECS_API +size_t ecs_iter_column_size( + const ecs_iter_t *it, + int32_t index); + +/** @} */ + + +/** + * @defgroup staging Staging + * @{ + */ + +/** 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 +FLECS_FLOAT ecs_frame_begin( + ecs_world_t *world, + FLECS_FLOAT 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); + +/** Begin staging. + * When an application does not use ecs_progress to control the main loop, it + * can still use Flecs features such as the defer queue. When an application + * needs to stage changes, it needs to call this function after ecs_frame_begin. + * A call to ecs_staging_begin must be followed by a call to ecs_staging_end. + * + * When staging is enabled, modifications to entities are stored to a stage. + * This ensures that arrays are not modified while iterating. Modifications are + * merged back to the "main stage" when ecs_staging_end is invoked. + * + * While the world is in staging mode, no structural changes (add/remove/...) + * can be made to the world itself. Operations must be executed on a stage + * instead (see ecs_get_stage). + * + * This function should only be ran from the main thread. + * + * @param world The world + * @return Whether world is currently staged. + */ +FLECS_API +bool ecs_staging_begin( + ecs_world_t *world); + +/** End staging. + * Leaves staging mode. After this operation the world may be directly mutated + * again. By default this operation also merges data back into the world, unless + * automerging was disabled explicitly. + * + * This function should only be ran from the main thread. + * + * @param world The world + */ +FLECS_API +void ecs_staging_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 progress() or after staging_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 + * defer_begin and 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. + */ +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. + */ +FLECS_API +bool ecs_is_deferred( + const ecs_world_t *world); + +/** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + * + * @param world The world. + * @return true if world changed from deferred mode to non-deferred mode. + */ +FLECS_API +bool ecs_defer_end( + ecs_world_t *world); + +/** Enable/disable automerging for world or stage. + * When automerging is enabled, staged data will automatically be merged with + * the world when staging ends. This happens at the end of progress(), at a + * sync point or when staging_end() is called. + * + * Applications can exercise more control over when data from a stage is merged + * by disabling automerging. This requires an application to explicitly call + * merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this function is + * invoked on a stage, automerging is only set for that specific stage. + * + * @param world The world. + * @param automerge Whether to enable or disable automerging. + */ +FLECS_API +void ecs_set_automerge( + ecs_world_t *world, + bool automerge); + +/** 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 set_stages() 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_stages( + ecs_world_t *world, + int32_t stages); + +/** Get number of configured stages. + * Return number of stages set by ecs_set_stages. + * + * @param world The world. + * @return The number of stages used for threading. + */ +FLECS_API +int32_t ecs_get_stage_count( + const ecs_world_t *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. + * + * @param world The world. + * @return The stage id. + */ +FLECS_API +int32_t ecs_get_stage_id( + 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 dediated 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); + +/** Get actual world from world. + * @param world A pointer to a stage or the world. + * @return The world. + */ +FLECS_API +const ecs_world_t* ecs_get_world( + const ecs_world_t *world); + +/** Test whether the current world object is readonly. + * This function allows the code to test whether the currently used world object + * is readonly or whether it allows for writing. + * + * @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 *stage); + +/** Create asynchronous stage. + * An asynchronous stage can be used to asynchronously queue operations for + * later merging with the world. An asynchronous stage is similar to a regular + * stage, except that it does not allow reading from the world. + * + * Asynchronous stages are never merged automatically, and must therefore be + * manually merged with the ecs_merge function. It is not necessary to call + * defer_begin or defer_end before and after enqueuing commands, as an + * asynchronous stage unconditionally defers operations. + * + * The application must ensure that no commands are added to the stage while the + * stage is being merged. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * + * @param world The world. + * @return The stage. + */ +FLECS_API +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world); + +/** Free asynchronous stage. + * The provided stage must be an asynchronous stage. If a non-asynchronous stage + * is provided, the operation will fail. + * + * @param stage The stage to free. + */ +FLECS_API +void ecs_async_stage_free( + ecs_world_t *stage); + +/** Test whether provided stage is asynchronous. + * + * @param stage The stage. + * @return True when the stage is asynchronous, false for a regular stage or + * world. + */ +FLECS_API +bool ecs_stage_is_async( + ecs_world_t *stage); + +/** @} */ + + +/** + * @defgroup table_functions Public table operations + * @brief Low-level table functions. These functions are intended to enable the + * creation of higher-level operations. It is not recommended to use + * these operations directly in application code as they do not provide + * the same safety guarantees as the other APIs. + * @{ + */ + +/** Find or create table with specified component string. + * The provided string must be a comma-separated list of fully qualified + * component identifiers. The returned table will have the specified components. + * Two lists that are the same but specify components in a different order will + * return the same table. + * + * @param world The world. + * @param type The components. + * @return The new or existing table, or NULL if the string contains an error. + */ +FLECS_API +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *type); + +/** Find or create table from type. + * Same as ecs_table_from_str, but provides the type directly. + * + * @param world The world. + * @param type The type. + * @return The new or existing table. + */ +FLECS_API +ecs_table_t* ecs_table_from_type( + ecs_world_t *world, + ecs_type_t type); + +/** Get type for table. + * + * @param table The table. + * @return The type of the table. + */ +FLECS_API +ecs_type_t ecs_table_get_type( + const ecs_table_t *table); + +/** Insert record into table. + * This will create a new record for the table, which inserts a value for each + * component. An optional entity and record can be provided. + * + * If a non-zero entity id is provided, a record must also be provided and vice + * versa. The record must be created by the entity index. If the provided record + * is not created for the specified entity, the behavior will be undefined. + * + * If the provided record is not managed by the entity index, the behavior will + * be undefined. + * + * The returned record contains a reference to the table and the table row. The + * data pointed to by the record is guaranteed not to move unless one or more + * rows are removed from this table. A row can be removed as result of a delete, + * or by adding/removing components from an entity stored in the table. + * + * @param world The world. + * @param table The table. + * @param entity The entity. + * @param record The entity-index record for the specified entity. + * @return A record containing the table and table row. + */ +FLECS_API +ecs_record_t ecs_table_insert( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record); + +/** 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 world The world. + * @param table The table. + * @return The number of records in a table. + */ +FLECS_API +int32_t ecs_table_count( + const ecs_table_t *table); + +/** 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); + +/** 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 or unlock table. + * When a table is locked, modifications to it will trigger an assert. When the + * table is locked recursively, it will take an equal amount of unlock + * operations to actually unlock the table. + * + * 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); + +/** Returns whether table is a module or contains module contents + * Returns true for tables that have module contents. Can be used to filter out + * tables that do not contain application data. + * + * @param table The table. + * @return true if table contains module contents, false if not. + */ +FLECS_API +bool ecs_table_has_module( + ecs_table_t *table); + +/** Commit (move) entity to a table. + * This operation moves an entity from its current table to the specified + * table. This may trigger the following actions: + * - Ctor for each component in the target table + * - Move for each overlapping component + * - Dtor for each component in the source table. + * - OnAdd triggers for non-overlapping components in the target table + * - OnRemove triggers for non-overlapping components in the source table. + * + * This operation is a faster than adding/removing components individually. + * + * The application must explicitly provide the difference in components between + * tables as the added/removed parameters. This can usually be derived directly + * from the result of ecs_table_add_id and esc_table_remove_id. These arrays are + * required to properly execute OnAdd/OnRemove triggers. + * + * @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, + ecs_ids_t *added, + ecs_ids_t *removed); + +/** @} */ + +/* Optional modules */ +#ifdef FLECS_SYSTEM +#include "flecs/modules/system.h" +#endif +#ifdef FLECS_PIPELINE +#include "flecs/modules/pipeline.h" +#endif +#ifdef FLECS_TIMER +#include "flecs/modules/timer.h" +#endif + +/* Optional addons */ +#ifdef FLECS_BULK +#include "flecs/addons/bulk.h" +#endif +#ifdef FLECS_MODULE +#include "flecs/addons/module.h" +#endif +#ifdef FLECS_PLECS +#include "flecs/addons/plecs.h" +#endif +#ifdef FLECS_PARSER +#include "flecs/addons/parser.h" +#endif +#ifdef FLECS_QUEUE +#include "flecs/addons/queue.h" +#endif +#ifdef FLECS_SNAPSHOT +#include "flecs/addons/snapshot.h" +#endif +#ifdef FLECS_DIRECT_ACCESS +#include "flecs/addons/direct_access.h" +#endif +#ifdef FLECS_STATS +#include "flecs/addons/stats.h" +#endif + +#ifdef __cplusplus +} + +#ifndef FLECS_NO_CPP +#ifndef FLECS_LEGACY +#include "flecs/flecs.hpp" +#endif +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/bulk.h b/fggl/ecs2/flecs/include/flecs/addons/bulk.h new file mode 100644 index 0000000000000000000000000000000000000000..a575ddb65c75be6badf775e4c4e4d6bad322a45d --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/bulk.h @@ -0,0 +1,137 @@ +/** + * @file bulk.h + * @brief Bulk operations operate on all entities that match a provided filter. + */ + +#ifdef FLECS_BULK + +#ifndef FLECS_BULK_H +#define FLECS_BULK_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Add an entity to entities matching a filter. + * This operation is the same as ecs_add_id, but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param entity_add The entity to add. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_entity( + ecs_world_t *world, + ecs_entity_t entity_add, + const ecs_filter_t *filter); + +/** Add a type to entities matching a filter. + * This operation is the same as ecs_add_type but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The type to add. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_type( + ecs_world_t *world, + ecs_type_t type, + const ecs_filter_t *filter); + +/** Add a component / type / tag to entities matching a filter. + * This operation is the same as ecs_add but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The component, type or tag to add. + * @param filter The filter. + */ +#define ecs_bulk_add(world, type, filter)\ + ecs_bulk_add_type(world, ecs_type(type), filter) + +/** Removes an entity from entities matching a filter. + * This operation is the same as ecs_remove_id, but is applied to all + * entities that match the provided filter. + * + * @param world The world. + * @param entity_remove The entity to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_remove_entity( + ecs_world_t *world, + ecs_entity_t entity_remove, + const ecs_filter_t *filter); + +/** Remove a type from entities matching a filter. + * This operation is the same as ecs_remove_type but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The type to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_remove_type( + ecs_world_t *world, + ecs_type_t type, + const ecs_filter_t *filter); + +/** Add a component / type / tag to entities matching a filter. + * This operation is the same as ecs_remove but is applied to all entities + * that match the provided filter. + * + * @param world The world. + * @param type The component, type or tag to remove. + * @param filter The filter. + */ +#define ecs_bulk_remove(world, type, filter)\ + ecs_bulk_remove_type(world, ecs_type(type), filter) + +/** Add / remove type from entities matching a filter. + * Combination of ecs_bulk_add_type and ecs_bulk_remove_type. + * + * @param world The world. + * @param to_add The type to add. + * @param to_remove The type to remove. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_add_remove_type( + ecs_world_t *world, + ecs_type_t to_add, + ecs_type_t to_remove, + const ecs_filter_t *filter); + +/** Add / remove component, type or tag from entities matching a filter. + * Combination of ecs_bulk_add and ecs_bulk_remove. + * + * @param world The world. + * @param to_add The component, type or tag to add. + * @param to_remove The component, type or tag to remove. + * @param filter The filter. + */ +#define ecs_bulk_add_remove(world, to_add, to_remove, filter)\ + ecs_bulk_add_remove_type(world, ecs_type(to_add), ecs_type(to_remove), filter) + +/** Delete entities matching a filter. + * This operation is the same as ecs_delete, but applies to all entities that + * match a filter. + * + * @param world The world. + * @param filter The filter. + */ +FLECS_API +void ecs_bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/deprecated.h b/fggl/ecs2/flecs/include/flecs/addons/deprecated.h new file mode 100644 index 0000000000000000000000000000000000000000..52ba9c902d57f3bd8dca5d4c733ad41260dcfce9 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/deprecated.h @@ -0,0 +1,565 @@ +/** + * @file 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 + +#define ecs_typeid(T) FLECS__E##T + +#define ecs_entity(T) ecs_typeid(T) + +#define ecs_add_trait(world, entity, component, trait)\ + ecs_add_entity(world, entity, ecs_trait(component, trait)) + +#define ecs_remove_trait(world, entity, component, trait)\ + ecs_remove_entity(world, entity, ecs_trait(component, trait)) + +#define ecs_has_trait(world, entity, component, trait)\ + ecs_has_entity(world, entity, ecs_trait(component, trait)) + +#ifndef FLECS_LEGACY + +#define ecs_set_trait(world, entity, component, trait, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_trait(ecs_typeid(component), ecs_typeid(trait)), sizeof(trait), &(trait)__VA_ARGS__) + +#define ecs_set_trait_tag(world, entity, trait, component, ...)\ + ecs_set_ptr_w_entity(world, entity, ecs_trait(ecs_typeid(component), trait), sizeof(component), &(component)__VA_ARGS__) + +#endif + +#define ecs_get_trait(world, entity, component, trait)\ + ((trait*)ecs_get_id(world, entity, ecs_trait(ecs_typeid(component), ecs_typeid(trait)))) + +#define ecs_get_trait_tag(world, entity, trait, component)\ + ((component*)ecs_get_id(world, entity, ecs_trait(ecs_typeid(component), trait))) + +#define ECS_PREFAB(world, id, ...) \ + ecs_entity_t id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .add_expr = #__VA_ARGS__,\ + .add = {EcsPrefab}\ + });\ + (void)id + +#define ECS_ENTITY_EXTERN(id)\ + extern ecs_entity_t id + +#define ECS_ENTITY_DECLARE(id)\ + ecs_entity_t id + +#define ECS_ENTITY_DEFINE(world, id, ...)\ + id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .add_expr = #__VA_ARGS__\ + });\ + +#define ECS_ENTITY(world, id, ...)\ + ecs_entity_t id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .add_expr = #__VA_ARGS__\ + });\ + (void)id + +#define ECS_COMPONENT(world, id) \ + ecs_id_t ecs_id(id) = ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + (void)ecs_id(id);\ + (void)ecs_type(id) + +#define ECS_COMPONENT_EXTERN(id)\ + extern ecs_id_t ecs_id(id);\ + extern ecs_type_t ecs_type(id) + +#define ECS_COMPONENT_DECLARE(id)\ + ecs_id_t ecs_id(id);\ + ecs_type_t ecs_type(id) + +#define ECS_COMPONENT_DEFINE(world, id)\ + ecs_id(id) = ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + ecs_type(id) = ecs_type_from_entity(world, ecs_id(id)) + +#define ECS_TAG(world, id)\ + ecs_entity_t id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .symbol = #id\ + });\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &id, 1);\ + (void)ecs_type(id) + +#define ECS_TAG_EXTERN(id)\ + extern ecs_entity_t id;\ + extern ecs_type_t ecs_type(id) + +#define ECS_TAG_DECLARE(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +#define ECS_TAG_DEFINE(world, id)\ + id = ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = #id,\ + .symbol = #id\ + });\ + ecs_type(id) = ecs_type_from_entity(world, id) + +#define ECS_TYPE(world, id, ...) \ + ecs_entity_t id = ecs_type_init(world, &(ecs_type_desc_t){\ + .entity.name = #id,\ + .ids_expr = #__VA_ARGS__\ + });\ + ecs_type_t ecs_type(id) = ecs_type_from_entity(world, id);\ + (void)id;\ + (void)ecs_type(id) + +#define ECS_TYPE_EXTERN(id)\ + extern ecs_entity_t id;\ + extern ecs_type_t ecs_type(id) + +#define ECS_TYPE_DECLARE(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +#define ECS_TYPE_DEFINE(world, id, ...)\ + id = ecs_type_init(world, &(ecs_type_desc_t){\ + .entity.name = #id,\ + .ids_expr = #__VA_ARGS__\ + });\ + ecs_type(id) = ecs_type_from_entity(world, id);\ + +#define ECS_COLUMN(it, type, id, column)\ + ecs_id_t ecs_id(type) = ecs_column_entity(it, column);\ + ecs_type_t ecs_type(type) = ecs_column_type(it, column);\ + type *id = ecs_column(it, type, column);\ + (void)ecs_id(type);\ + (void)ecs_type(type);\ + (void)id + +#define ECS_COLUMN_COMPONENT(it, id, column)\ + ecs_id_t ecs_id(id) = ecs_column_entity(it, column);\ + ecs_type_t ecs_type(id) = ecs_column_type(it, column);\ + (void)ecs_id(id);\ + (void)ecs_type(id) + +#define ECS_COLUMN_ENTITY(it, id, column)\ + ecs_entity_t id = ecs_column_entity(it, column);\ + ecs_type_t ecs_type(id) = ecs_column_type(it, column);\ + (void)id;\ + (void)ecs_type(id) + +#define ECS_IMPORT_COLUMN(it, module, column) \ + module *ecs_module_ptr(module) = ecs_column(it, module, column);\ + ecs_assert(ecs_module_ptr(module) != NULL, ECS_MODULE_UNDEFINED, #module);\ + ecs_assert(!ecs_is_owned(it, column), ECS_COLUMN_IS_NOT_SHARED, NULL);\ + module ecs_module(module) = *ecs_module_ptr(module);\ + module##ImportHandles(ecs_module(module)) + +#define ecs_new(world, type) ecs_new_w_type(world, ecs_type(type)) + +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_type(world, ecs_type(component), count) + +#define ecs_add(world, entity, component)\ + ecs_add_type(world, entity, ecs_type(component)) + +#define ecs_remove(world, entity, type)\ + ecs_remove_type(world, entity, ecs_type(type)) + +#define ecs_add_remove(world, entity, to_add, to_remove)\ + ecs_add_remove_type(world, entity, ecs_type(to_add), ecs_type(to_remove)) + +#define ecs_has(world, entity, type)\ + ecs_has_type(world, entity, ecs_type(type)) + +#define ecs_owns(world, entity, type, owned)\ + ecs_type_owns_type(world, ecs_get_type(world, entity), ecs_type(type), owned) + +#define ecs_set_ptr_w_id(world, entity, size, ptr)\ + ecs_set_id(world, entity, size, ptr) + +#define ecs_owns_entity(world, entity, id, owned)\ + ecs_type_has_id(world, ecs_get_type(world, entity), id, owned) + +typedef ecs_ids_t ecs_entities_t; + +ECS_DEPRECATED("deprecated functionality") +FLECS_API +void ecs_dim_type( + ecs_world_t *world, + ecs_type_t type, + int32_t entity_count); + +ECS_DEPRECATED("use ecs_new_w_id") +FLECS_API +ecs_entity_t ecs_new_w_type( + ecs_world_t *world, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_bulk_new_w_id") +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_type( + ecs_world_t *world, + ecs_type_t type, + int32_t count); + +ECS_DEPRECATED("use ecs_add_id") +FLECS_API +void ecs_add_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_remove_id") +FLECS_API +void ecs_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_add_remove_id") +FLECS_API +void ecs_add_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t to_add, + ecs_type_t to_remove); + +ECS_DEPRECATED("use ecs_has_id") +FLECS_API +bool ecs_has_type( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_count_filter") +FLECS_API +int32_t ecs_count_type( + const ecs_world_t *world, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_count_id") +FLECS_API +int32_t ecs_count_entity( + const ecs_world_t *world, + ecs_id_t entity); + +ECS_DEPRECATED("use ecs_count_filter") +FLECS_API +int32_t ecs_count_w_filter( + const ecs_world_t *world, + const ecs_filter_t *filter); + +ECS_DEPRECATED("use ecs_set_component_actions_w_entity") +FLECS_API +void ecs_set_component_actions_w_entity( + ecs_world_t *world, + ecs_id_t id, + EcsComponentLifecycle *actions); + +ECS_DEPRECATED("use ecs_new_w_id") +FLECS_API +ecs_entity_t ecs_new_w_entity( + ecs_world_t *world, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_bulk_new_w_id") +FLECS_API +const ecs_entity_t* ecs_bulk_new_w_entity( + ecs_world_t *world, + ecs_id_t id, + int32_t count); + +ECS_DEPRECATED("use ecs_enable_component_w_id") +FLECS_API +void ecs_enable_component_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable); + +ECS_DEPRECATED("use ecs_is_component_enabled_w_id") +FLECS_API +bool ecs_is_component_enabled_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_id") +FLECS_API +const void* ecs_get_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_id") +FLECS_API +const void* ecs_get_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_ref_w_id") +FLECS_API +const void* ecs_get_ref_w_entity( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_get_mut_id") +FLECS_API +void* ecs_get_mut_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added); + +ECS_DEPRECATED("use ecs_get_mut_id") +FLECS_API +void* ecs_get_mut_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added); + +ECS_DEPRECATED("use ecs_modified_id") +FLECS_API +void ecs_modified_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_modified_id") +void ecs_modified_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_set_id") +FLECS_API +ecs_entity_t ecs_set_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr); + +ECS_DEPRECATED("use ecs_has_id") +FLECS_API +bool ecs_has_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_id_str") +FLECS_API +size_t ecs_entity_str( + const ecs_world_t *world, + ecs_id_t entity, + char *buffer, + size_t buffer_len); + +ECS_DEPRECATED("use ecs_get_object_w_id(world, entity, EcsChildOf, id)") +FLECS_API +ecs_entity_t ecs_get_parent_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +#define ecs_get_parent(world, entity, component)\ + ecs_get_parent_w_entity(world, entity, ecs_typeid(component)) + +ECS_DEPRECATED("use ecs_get_stage_id") +FLECS_API +int32_t ecs_get_thread_index( + const ecs_world_t *world); + +ECS_DEPRECATED("use ecs_add_id") +FLECS_API +void ecs_add_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t entity_add); + +ECS_DEPRECATED("use ecs_remove_id") +FLECS_API +void ecs_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id); + +ECS_DEPRECATED("use ecs_add_id / ecs_remove_id") +FLECS_API +void ecs_add_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id_add, + ecs_id_t id_remove); + +ECS_DEPRECATED("use ecs_type_from_id") +FLECS_API +ecs_type_t ecs_type_from_entity( + ecs_world_t *world, + ecs_entity_t entity); + +ECS_DEPRECATED("use ecs_type_to_id") +FLECS_API +ecs_entity_t ecs_type_to_entity( + const ecs_world_t *world, + ecs_type_t type); + +ECS_DEPRECATED("use ecs_type_has_id") +FLECS_API +bool ecs_type_has_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity); + +ECS_DEPRECATED("use ecs_type_has_id") +FLECS_API +bool ecs_type_owns_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned); + +ECS_DEPRECATED("use ecs_term/ecs_term_w_size") +FLECS_API +void* ecs_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column); + +#define ecs_column(it, T, column)\ + ecs_column_w_size(it, sizeof(T), column) + +ECS_DEPRECATED("no replacement") +FLECS_API +int32_t ecs_column_index_from_name( + const ecs_iter_t *it, + const char *name); + +ECS_DEPRECATED("use ecs_term_source") +FLECS_API +ecs_entity_t ecs_column_source( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_id") +FLECS_API +ecs_entity_t ecs_column_entity( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("no replacement") +FLECS_API +ecs_type_t ecs_column_type( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_size") +FLECS_API +size_t ecs_column_size( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_is_readonly") +FLECS_API +bool ecs_is_readonly( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_term_is_owned") +FLECS_API +bool ecs_is_owned( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_iter_column") +FLECS_API +void* ecs_table_column( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_iter_column_size") +FLECS_API +size_t ecs_table_column_size( + const ecs_iter_t *it, + int32_t column); + +ECS_DEPRECATED("use ecs_iter_column_index") +FLECS_API +int32_t ecs_table_component_index( + const ecs_iter_t *it, + ecs_entity_t component); + +ECS_DEPRECATED("use ecs_set_rate") +FLECS_API +ecs_entity_t ecs_set_rate_filter( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +ecs_query_t* ecs_query_new( + ecs_world_t *world, + const char *sig); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +ecs_query_t* ecs_subquery_new( + ecs_world_t *world, + ecs_query_t *parent, + const char *sig); + +ECS_DEPRECATED("use ecs_query_deinit") +FLECS_API +void ecs_query_free( + ecs_query_t *query); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +void ecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t component, + ecs_order_by_action_t compare); + +ECS_DEPRECATED("use ecs_query_init") +FLECS_API +void ecs_query_group_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t component, + ecs_group_by_action_t rank_action); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/deprecated/entity.hpp b/fggl/ecs2/flecs/include/flecs/addons/deprecated/entity.hpp new file mode 100644 index 0000000000000000000000000000000000000000..02fc49a7545b44b9bf50141e7de45bc86867046b --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/deprecated/entity.hpp @@ -0,0 +1,231 @@ + +namespace flecs +{ + +struct entity_builder_deprecated_tag { }; + +/** Deprecated functions */ +template <typename Base> +class entity_builder_deprecated : public entity_builder_base<entity_builder_deprecated_tag, Base> { +public: + template<typename T, typename C> + ECS_DEPRECATED("use add<Relation, Object>") + const Base& add_trait() const { + ecs_add_pair(this->base_world(), this->base_id(), + _::cpp_type<T>::id(this->base_world()), + _::cpp_type<C>::id(this->base_world())); + return *this; + } + + template<typename T> + ECS_DEPRECATED("use add<Relation>(const entity&)") + const Base& add_trait(const Base& c) const { + ecs_add_pair(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world()), c.id()); + return *this; + } + + template<typename C> + ECS_DEPRECATED("use add_object<Object>(const entity&)") + const Base& add_trait_tag(const Base& t) const { + ecs_add_pair(this->base_world(), this->base_id(), t.id(), _::cpp_type<C>::id(this->base_world())); + return *this; + } + + ECS_DEPRECATED("use add(const entity&, const entity&)") + const Base& add_trait(const Base& t, const Base& c) const { + ecs_add_pair(this->base_world(), this->base_id(), t.id(), c.id()); + return *this; + } + + template<typename T, typename C> + ECS_DEPRECATED("use remove<Relation, Object>") + const Base& remove_trait() const { + ecs_remove_pair(this->base_world(), this->base_id(), + _::cpp_type<T>::id(this->base_world()), + _::cpp_type<C>::id(this->base_world())); + return *this; + } + + template<typename T> + ECS_DEPRECATED("use remove<Relation>(const entity&)") + const Base& remove_trait(const Base& c) const { + ecs_remove_pair(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world()), c.id()); + return *this; + } + + template<typename C> + ECS_DEPRECATED("use remove_object<Object>(const entity&)") + const Base& remove_trait_tag(const Base& t) const { + ecs_remove_pair(this->base_world(), this->base_id(), t.id(), _::cpp_type<C>::id(this->base_world())); + return *this; + } + + ECS_DEPRECATED("use remove(const entity&, const entity&)") + const Base& remove_trait(const Base& t, const Base& c) const { + ecs_remove_pair(this->base_world(), this->base_id(), t.id(), c.id()); + return *this; + } + + template <typename T, typename C> + ECS_DEPRECATED("use set<Relation, Object>(const Relation&)") + const Base& set_trait(const T& value) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_set_ptr_w_entity(this->base_world(), this->base_id(), + ecs_pair(comp_id, _::cpp_type<C>::id(this->base_world())), + sizeof(T), &value); + + return *this; + } + + template <typename T> + ECS_DEPRECATED("use set<Relation>(const entity&, const Relation&)") + const Base& set_trait(const T& value, const Base& c) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_set_ptr_w_entity(this->base_world(), this->base_id(), + ecs_pair(comp_id, c.id()), + sizeof(T), &value); + + return *this; + } + + template <typename C> + ECS_DEPRECATED("use set_object<Object>(const entity&, const Object&)") + const Base& set_trait_tag(const Base& t, const C& value) const { + auto comp_id = _::cpp_type<C>::id(this->base_world()); + ecs_assert(_::cpp_type<C>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_set_ptr_w_entity(this->base_world(), this->base_id(), + ecs_pair(t.id(), comp_id), + sizeof(C), &value); + + return *this; + } + + ECS_DEPRECATED("use set(Func func)") + template <typename T, typename Func> + const Base& patch(const Func& func) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + + ecs_assert(_::cpp_type<T>::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + bool is_added; + T *ptr = static_cast<T*>(ecs_get_mut_w_entity( + this->base_world(), this->base_id(), comp_id, &is_added)); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + func(*ptr); + ecs_modified_w_entity(this->base_world(), this->base_id(), comp_id); + + return *this; + } +}; + +struct entity_deprecated_tag { }; + +template<typename Base> +class entity_deprecated : entity_builder_base<entity_deprecated_tag, Base> { +public: + template<typename T, typename C> + ECS_DEPRECATED("use get<Relation, Object>") + const T* get_trait() const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<const T*>(ecs_get_id(this->base_world(), this->base_id(), ecs_trait( + _::cpp_type<C>::id(this->base_world()), comp_id))); + } + + template<typename T> + ECS_DEPRECATED("use get<Relation>(const entity&)") + const T* get_trait(const Base& c) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<const T*>(ecs_get_id(this->base_world(), this->base_id(), ecs_trait( + c.id(), comp_id))); + } + + template<typename C> + ECS_DEPRECATED("use get_object<Object>(const entity&)") + const C* get_trait_tag(const Base& t) const { + auto comp_id = _::cpp_type<C>::id(this->base_world()); + ecs_assert(_::cpp_type<C>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<const C*>(ecs_get_id(this->base_world(), this->base_id(), ecs_trait( + comp_id, t.id()))); + } + + ECS_DEPRECATED("use get(const entity&, const entity&)") + const void* get_trait(const Base& t, const Base& c) const{ + return ecs_get_id(this->base_world(), this->base_id(), ecs_trait(c.id(), t.id())); + } + + template <typename T, typename C> + ECS_DEPRECATED("use get_mut<Relation, Object>(bool)") + T* get_trait_mut(bool *is_added = nullptr) const { + auto t_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<T*>( + ecs_get_mut_w_entity( + this->base_world(), this->base_id(), ecs_trait(_::cpp_type<C>::id(this->base_world()), + t_id), is_added)); + } + + template <typename T> + ECS_DEPRECATED("use get_mut<Relation>(const entity&, bool)") + T* get_trait_mut(const Base& c, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<T>::id(this->base_world()); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<T*>( + ecs_get_mut_w_entity( + this->base_world(), this->base_id(), ecs_trait( comp_id, c.id()), is_added)); + } + + template <typename C> + ECS_DEPRECATED("use get_mut_object<Object>(const entity&, bool)") + C* get_trait_tag_mut(const Base& t, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<C>::id(this->base_world()); + ecs_assert(_::cpp_type<C>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + return static_cast<C*>( + ecs_get_mut_w_entity( + this->base_world(), this->base_id(), ecs_trait(comp_id, t.id()), is_added)); + } + + template<typename T, typename C> + ECS_DEPRECATED("use has<Relation, Object>") + bool has_trait() const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + _::cpp_type<C>::id(this->base_world()), + _::cpp_type<T>::id(this->base_world()))); + } + + template<typename T> + ECS_DEPRECATED("use has<Relation>(const flecs::entity&)") + bool has_trait(const Base& component) const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + component.id(), _::cpp_type<T>::id(this->base_world()))); + } + + template<typename C> + ECS_DEPRECATED("use has_object<Object>(const flecs::entity&)") + bool has_trait_tag(const Base& trait) const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + _::cpp_type<C>::id(this->base_world()), trait.id())); + } + + ECS_DEPRECATED("use has(const flecs::entity&, const flecs::entity&)") + bool has_trait(const Base& trait, const Base& e) const { + return ecs_has_entity(this->base_world(), this->base_id(), ecs_trait( + e.id(), trait.id())); + } +}; + +} diff --git a/fggl/ecs2/flecs/include/flecs/addons/deprecated/iter.hpp b/fggl/ecs2/flecs/include/flecs/addons/deprecated/iter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7b2ba2d091e52acc2cea390d25e42da83f372d16 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/deprecated/iter.hpp @@ -0,0 +1,86 @@ + +namespace flecs +{ + +/* Deprecated functions */ +template<typename Base> +class iter_deprecated { +public: + ECS_DEPRECATED("use term_count(int32_t)") + int32_t column_count() const { + return base()->term_count(); + } + + ECS_DEPRECATED("use term_size(int32_t)") + size_t column_size(int32_t col) const { + return base()->term_size(col); + } + + ECS_DEPRECATED("use is_owned(int32_t)") + bool is_shared(int32_t col) const { + return !base()->is_owned(col); + } + + ECS_DEPRECATED("use term_source(int32_t)") + flecs::entity column_source(int32_t col) const; + + ECS_DEPRECATED("use term_id(int32_t)") + flecs::entity column_entity(int32_t col) const; + + ECS_DEPRECATED("no replacement") + flecs::type column_type(int32_t col) const; + + ECS_DEPRECATED("use type()") + type table_type() const; + + template <typename T, if_t< is_const<T>::value > = 0> + ECS_DEPRECATED("use term<const T>(int32_t)") + flecs::column<T> column(int32_t col) const { + return base()->template term<T>(col); + } + + template <typename T, if_not_t< is_const<T>::value > = 0> + ECS_DEPRECATED("use term<T>(int32_t)") + flecs::column<T> column(int32_t col) const { + ecs_assert(!ecs_is_readonly(iter(), col), + ECS_COLUMN_ACCESS_VIOLATION, NULL); + return base()->template term<T>(col); + } + + ECS_DEPRECATED("use term(int32_t)") + flecs::unsafe_column column(int32_t col) const { + return base()->term(col); + } + + template <typename T> + ECS_DEPRECATED("use owned<T>(int32_t)") + flecs::column<T> owned(int32_t col) const { + return base()->template owned<T>(col); + } + + template <typename T> + ECS_DEPRECATED("use shared<T>(int32_t)") + const T& shared(int32_t col) const { + return base()->template shared<T>(col); + } + + template <typename T, if_t< is_const<T>::value > = 0> + ECS_DEPRECATED("no replacement") + T& element(int32_t col, int32_t row) const { + return base()->template get_element<T>(col, row); + } + + template <typename T, if_not_t< is_const<T>::value > = 0> + ECS_DEPRECATED("no replacement") + T& element(int32_t col, int32_t row) const { + ecs_assert(!ecs_is_readonly(iter(), col), + ECS_COLUMN_ACCESS_VIOLATION, NULL); + return base()->template get_element<T>(col, row); + } + +private: + const Base* base() const { return static_cast<const Base*>(this); } + const flecs::iter_t* iter() const { return base()->c_ptr(); } +}; + +} diff --git a/fggl/ecs2/flecs/include/flecs/addons/deprecated/type.hpp b/fggl/ecs2/flecs/include/flecs/addons/deprecated/type.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f62eb78bccaf597288e1a7b180197dc69c84b0f2 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/deprecated/type.hpp @@ -0,0 +1,44 @@ + +namespace flecs +{ + +/* Deprecated functions */ +template<typename Base> +class type_deprecated { +public: + + template <typename T, typename C> + ECS_DEPRECATED("use add<Relation, Object>") + type& add_trait() { + static_cast<Base*>(this)->add(ecs_pair( + _::cpp_type<T>::id(world()), + _::cpp_type<C>::id(world()))); + return *base(); + } + + template <typename T> + ECS_DEPRECATED("use add<Relation>(const flecs::entity&)") + type& add_trait(const flecs::entity& c) { + static_cast<Base*>(this)->add(ecs_pair(_::cpp_type<T>::id(world()), c.id())); + return *base(); + } + + ECS_DEPRECATED("use add(const flecs::entity&, const flecs::entity&)") + type& add_trait(const flecs::entity& t, const flecs::entity& c) { + static_cast<Base*>(this)->add(ecs_pair(t.id(), c.id())); + return *base(); + } + + template <typename C> + ECS_DEPRECATED("use add_object<Object>(const flecs::entity&)") + type& add_trait_tag(const flecs::entity& t) { + static_cast<Base*>(this)->add(ecs_pair(t.id(), _::cpp_type<C>::id(world()))); + return *base(); + } + +private: + Base* base() { return static_cast<Base*>(this); } + flecs::world_t* world() { return base()->world().c_ptr(); } +}; + +} diff --git a/fggl/ecs2/flecs/include/flecs/addons/direct_access.h b/fggl/ecs2/flecs/include/flecs/addons/direct_access.h new file mode 100644 index 0000000000000000000000000000000000000000..bc59cbf39714dab9ca0a88293a4d578f8b9ebf36 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/direct_access.h @@ -0,0 +1,327 @@ +/** + * @file direct_access.h + * @brief Low-level access to underlying data structures for best performance. + * + * This API allows for low-level direct access to tables and their columns. The + * APIs primary intent is to provide fast primitives for new operations. It is + * not recommended to use the API directly in application code, as invoking the + * API in an incorrect way can lead to a corrupted datastore. + */ + +#ifdef FLECS_DIRECT_ACCESS + +#ifndef FLECS_DIRECT_ACCESS_H_ +#define FLECS_DIRECT_ACCESS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Find the index of a column in a table. + * Table columns are stored in the order of their respective component ids. As + * this is not trivial for an application to deduce, this operation returns the + * index of a column in a table for a given component. This index can be used + * in other table operations to identify a column. + * + * The returned index is determined separately for each table. Indices obtained + * for one table should not be used for another table. + * + * @param table The table. + * @param component The component for which to retrieve the column index. + * @return The column index, or -1 if the table does not have the component. + */ +FLECS_API +int32_t ecs_table_find_column( + const ecs_table_t *table, + ecs_entity_t component); + +/** Get table column. + * This operation returns the pointer to a column array. A column contains all + * the data for a component for the provided table in a contiguous array. + * + * The returned pointer is not stable, and may change when a table needs to + * resize its arrays, for example in order to accomodate for more records. + * + * @param table The table. + * @param column The column index. + * @return Vector that contains the column array. + */ +FLECS_API +ecs_vector_t* ecs_table_get_column( + const ecs_table_t *table, + int32_t column); + +/** Set table column. + * This operation enables an application to set a component column for a table. + * After the operation the column is owned by the table. Any operations that + * change the column after this operation can cause undefined behavior. + * + * Care must be taken that all columns in a table have the same number of + * elements. If one column has less elements than another, the behavior is + * undefined. The operation will not check if the assigned column is of the same + * size as other columns, as this would prevent an application from assigning + * a set of different columns to a table of a different size. + * + * Setting a column will not delete the previous column. It is the + * responsibility of the application to ensure that the old column is deleted + * properly (using ecs_table_delete_column). + * + * The provided vector must have the same element size and alignment as the + * target column. If the size and/or alignment do not match, the behavior will + * be undefined. In debug mode the operation may assert. + * + * If the provided vector is NULL, the table will ensure that a vector is + * created for the provided column. If a vector exists that is not of the + * same size as the entities vector, it will be resized to match. + * + * @param world The world. + * @param table The table. + * @param column The column index. + * @param vector The column data to assing. + */ +FLECS_API +ecs_vector_t* ecs_table_set_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector); + +/** Get the vector containing entity ids for the table. + * This operation obtains the vector with entity ids for the current table. Each + * entity id is associated with one record, and ids are stored in the same order + * as the table records. The element type of the vector is ecs_entity_t. + * + * @param table The table. + * @return The vector containing the table's entities. + */ +FLECS_API +ecs_vector_t* ecs_table_get_entities( + const ecs_table_t *table); + +/** Get the vector containing pointers to entity records. + * A table stores cached pointers to entity records for fast access. This + * operation provides direct access to the vector. The element type of the + * vector is ecs_record_t*. + * + * @param table The table. + * @return The vector containing the entity records. + */ +FLECS_API +ecs_vector_t* ecs_table_get_records( + const ecs_table_t *table); + +/** Clear records. + * This operation clears records for a world so that they no longer point to a + * table. This is useful to ensure that a world is left in a consistent state + * after moving data to destination world. + * + * @param records The vector with record pointers + */ +FLECS_API +void ecs_records_clear( + ecs_vector_t *records); + +/** Initialize records. + * This operation ensures entity records are updated to the provided table. + * + * @param world The world. + * @param entities The vector with entity identifiers. + * @param records The vector with record pointers. + * @param table The table in which the entities are stored. + */ +FLECS_API +void ecs_records_update( + ecs_world_t *world, + ecs_vector_t *entities, + ecs_vector_t *records, + ecs_table_t *table); + +/** Set the vector containing entity ids for the table. + * This operation sets the vector with entity ids for a table. In addition the + * operation also requires setting a vector with pointers to records. The + * record pointers in the vector need to be managed by the entity index. If they + * are not, this can cause undefined behavior. + * + * The provided vectors must have the same number of elements as the number of + * records in the table. If the element count is not the same, this causes + * undefined behavior. + * + * A table must have an entity and record vector, even if the table does not + * contain entities. For each record that is not an entity, the entity vector + * should contain 0, and the record vector should contain NULL. + * + * @param table The table. + * @param entities The entity vector. + * @param records The record vector. + */ +FLECS_API +void ecs_table_set_entities( + ecs_table_t *table, + ecs_vector_t *entities, + ecs_vector_t *records); + +/** Delete a column. + * This operation frees the memory of a table column and will invoke the + * component destructor if registered. + * + * The provided vector does not need to be the same as the vector in the table. + * The reason the table must be provided is so that the operation can retrieve + * the correct destructor for the component. If the component does not have a + * destructor, an application can alternatively delete the vector directly. + * + * If the specified vector is NULL, the column of the table will be removed and + * the table will be updated to no longer point at the column. If an explicit + * column is provided, the table is not modified. If a column is deleted that is + * still being pointed to by a table, behavior is undefined. It is the + * responsibility of the application to ensure that a table no longer points to + * a deleted column, by using ecs_table_set_column. + * + * Simultaneously, if this operation is used to delete a table column, the + * application should make sure that if the table contains other columns, they + * are either also deleted, or that the deleted column is replaced by a column + * of the same size. Note that this also goes for the entity and record vectors, + * they should have the same number of elements as the other columns. + * + * The vector must be of the same component as the specified column. If the + * vector is not of the same component, behavior will be undefined. In debug + * mode the API may assert, though it may not always be able to detect a + * mismatching vector/column. + * + * After this operation the vector should no longer be used by the application. + * + * @param table The table. + * @param column The column index. + * @param vector The column vector to delete. + */ +FLECS_API +void ecs_table_delete_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector); + +/** Find a record for a given entity. + * This operation finds an existing record in the entity index for a given + * entity. The returned pointer is stable for the lifecycle of the world and can + * be used as argument for the ecs_record_update operation. + * + * The returned record (if found) points to the adminstration that relates an + * entity id to a table. Updating the value of the returned record will cause + * operations like ecs_get and ecs_has to look in the updated table. + * + * Updating this record to a table in which the entity is not stored causes + * undefined behavior. + * + * When the entity has never been created or is not alive this operation will + * return NULL. + * + * @param world The world. + * @param entity The entity. + * @return The record that belongs to the entity, or NULL if not found. + */ +FLECS_API +ecs_record_t* ecs_record_find( + ecs_world_t *world, + ecs_entity_t entity); + +/** Same as ecs_record_find, but creates record if it doesn't exist. + * If an entity id has not been created with ecs_new_*, this function can be + * used to ensure that a record exists for an entity id. If the provided id + * already exists in the world, the operation will return the existing record. + * + * @param world The world. + * @param entity The entity for which to retrieve the record. + * @return The (new or existing) record that belongs to the entity. + */ +FLECS_API +ecs_record_t* ecs_record_ensure( + ecs_world_t *world, + ecs_entity_t entity); + +/** Get value from record. + * This operation gets a component value from a record. The provided column + * index must match the table of the record. + * + * @param r The record. + * @param column The column index of the component to get. + */ +FLECS_API +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t size); + +/** Copy value to a component for a record. + * This operation sets the component value of a single component for a record. + * If the component type has a copy action it will be used, otherwise the value + * be memcpyd into the component array. + * + * The provided record does not need to be managed by the entity index but does + * need to point to a valid record in the table. If the provided index is + * outside of the range indicating the number of records in the table, behavior + * is undefined. In debug mode it will cause the operation to assert. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to copy. + */ +FLECS_API +void ecs_record_copy_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + const void *value, + int32_t count); + +/** Memcpy value to a component for a record. + * Same as ecs_record_copy_to, except that this operation will always use + * memcpy. This operation should only be used for components that can be safely + * memcpyd. If the operation is used for a component that has a copy or move + * action, the behavior is undefined. In debug mode the operation may assert. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to move. + */ +FLECS_API +void ecs_record_copy_pod_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + const void *value, + int32_t count); + +/** Move value to a component for a record. + * Same as ecs_record_copy_to, except that it uses the move action. If the + * component has no move action the value will be memcpyd into the component + * array. After this operation the application can no longer assume that the + * value passed into the function is valid. + * + * @param world The world. + * @param r The record to set. + * @param column The column index of the component to set. + * @param size The size of the component. + * @param value Pointer to the value to move. + */ +FLECS_API +void ecs_record_move_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t size, + void *value, + int32_t count); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/module.h b/fggl/ecs2/flecs/include/flecs/addons/module.h new file mode 100644 index 0000000000000000000000000000000000000000..5253cdb406251fc0bf77f528a4ddbabba51cf18c --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/module.h @@ -0,0 +1,185 @@ +/** + * @file module.h + * @brief Module addon. + * + * The module addon allows for creating and importing modules. Flecs modules + * enable applications to organize components and systems into reusable units of + * code that can easily be across projects. + */ + +#ifdef FLECS_MODULE + +#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 to load. + * @param module_name The name of the module to load. + * @param flags An integer that will be passed into the module import action. + * @param handles_out A struct with handles to the module components/systems. + * @param handles_size Size of the handles_out parameter. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name, + void *handles_out, + size_t handles_size); + +/* 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 ecs_component_desc_t *desc); + +/** Define module + */ +#define ECS_MODULE(world, id)\ + ecs_entity_t ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .name = #id,\ + .add = {EcsModule}\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + });\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + id *handles = (id*)ecs_get_mut(world, ecs_id(id), id, NULL);\ + ecs_set_scope(world, ecs_id(id));\ + (void)ecs_id(id);\ + (void)ecs_type(id);\ + (void)handles; + +/** Wrapper around ecs_import. + * This macro provides a convenient way to load a module with the world. It can + * be used like this: + * + * ECS_IMPORT(world, FlecsSystemsPhysics, 0); + * + * This macro will define entity and type handles for the component associated + * with the module. The module component will be created as a singleton. + * + * The contents of a module component are module specific, although they + * typically contain handles to the content of the module. + */ +#define ECS_IMPORT(world, id) \ + id ecs_module(id);\ + char *id##__name = ecs_module_path_from_c(#id);\ + ecs_id_t ecs_id(id) = ecs_import(\ + world, id##Import, id##__name, &ecs_module(id), sizeof(id));\ + ecs_os_free(id##__name);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + id##ImportHandles(ecs_module(id));\ + (void)ecs_id(id);\ + (void)ecs_type(id);\ + +/** Utility macro for declaring a component inside a handles type */ +#define ECS_DECLARE_COMPONENT(id)\ + ecs_id_t ecs_id(id);\ + ecs_type_t ecs_type(id) + +/** Utility macro for declaring an entity inside a handles type */ +#define ECS_DECLARE_ENTITY(id)\ + ecs_entity_t id;\ + ecs_type_t ecs_type(id) + +/** Utility macro for declaring a type inside a handles type */ +#define ECS_DECLARE_TYPE(id)\ + ECS_DECLARE_ENTITY(id) + +/** Utility macro for setting a component in a module function */ +#define ECS_SET_COMPONENT(id)\ + if (handles) handles->ecs_id(id) = ecs_id(id);\ + if (handles) handles->ecs_type(id) = ecs_type(id) + +/** Utility macro for setting an entity in a module function */ +#define ECS_SET_ENTITY(id)\ + if (handles) handles->id = id; + +/** Utility macro for setting a type in a module function */ +#define ECS_SET_TYPE(id)\ + if (handles) handles->id = id;\ + if (handles) handles->ecs_type(id) = ecs_type(id); + +#define ECS_EXPORT_COMPONENT(id)\ + ECS_SET_COMPONENT(id) + +#define ECS_EXPORT_ENTITY(id)\ + ECS_SET_ENTITY(id) + +#define ECS_EXPORT_TYPE(id)\ + ECS_SET_TYPE(id) + +/** Utility macro for importing a component */ +#define ECS_IMPORT_COMPONENT(handles, id)\ + ecs_id_t ecs_id(id) = (handles).ecs_id(id); (void)ecs_id(id);\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &FLECS__E##id, 1);\ + (void)ecs_id(id);\ + (void)ecs_type(id) + +/** Utility macro for importing an entity */ +#define ECS_IMPORT_ENTITY(handles, id)\ + ecs_entity_t id = (handles).id;\ + ECS_VECTOR_STACK(FLECS__T##id, ecs_entity_t, &id, 1);\ + (void)id;\ + (void)ecs_type(id) + +/** Utility macro for importing a type */ +#define ECS_IMPORT_TYPE(handles, id)\ + ecs_entity_t id = (handles).id;\ + ecs_type_t ecs_type(id) = (handles).ecs_type(id);\ + (void)id;\ + (void)ecs_type(id) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/parser.h b/fggl/ecs2/flecs/include/flecs/addons/parser.h new file mode 100644 index 0000000000000000000000000000000000000000..fc6cac7556eec13789793fc386931fc80d5b7814 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/parser.h @@ -0,0 +1,59 @@ +/** + * @file parser.h + * @brief Parser addon. + * + * The parser addon parses string expressions into lists of terms, and can be + * used to construct filters, queries and types. + */ + +#ifdef FLECS_PARSER + +#ifndef FLECS_PARSER_H +#define FLECS_PARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Parse term in expression. + * This operation parses a single term in an expression and returns a pointer + * to the next term expression. + * + * If the returned pointer points to the 0-terminator, the expression is fully + * parsed. The function would typically be called in a while loop: + * + * const char *ptr = expr; + * while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { } + * + * The operation does not attempt to find entity ids from the names in the + * expression. Use the ecs_term_resolve_ids function to resolve the identifiers + * in the parsed term. + * + * The returned term will in most cases contain allocated resources, which + * should freed (or used) by the application. To free the resources for a term, + * use the ecs_term_free function. + * + * The parser accepts expressions in the legacy string format. + * + * @param world The world. + * @param name The name of the expression (optional, improves error logs) + * @param expr The expression to parse (optional, improves error logs) + * @param ptr The pointer to the current term (must be in expr). + * @param term_out Out parameter for the term. + * @return pointer to next term if successful, NULL if failed. + */ +FLECS_API +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term_out); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // FLECS_PARSER_H + +#endif // FLECS_PARSER diff --git a/fggl/ecs2/flecs/include/flecs/addons/plecs.h b/fggl/ecs2/flecs/include/flecs/addons/plecs.h new file mode 100644 index 0000000000000000000000000000000000000000..108fb6c127de75d4e6e4ddbc0246bb9146fb459e --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/plecs.h @@ -0,0 +1,80 @@ +/** + * @file pecs.h + * @brief Plecs addon. + * + * Plecs is a small data definition language for instantiating entities that + * reuses the existing flecs query parser. The following examples illustrate + * how a plecs snippet translates to regular flecs operations: + * + * Plecs: + * Entity + * C code: + * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * + * Plecs: + * Position(Entity) + * C code: + * ecs_entity_t Position = ecs_set_name(world, 0, "Position"); + * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * ecs_add_id(world, Entity, Position); + * + * Plecs: + * Likes(Entity, Apples) + * C code: + * ecs_entity_t Likes = ecs_set_name(world, 0, "Likes"); + * ecs_entity_t Apples = ecs_set_name(world, 0, "Apples"); + * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * ecs_add_pair(world, Entity, Likes, Apples); + * + * A plecs string may contain multiple statements, separated by a newline: + * Likes(Entity, Apples) + * Likes(Entity, Pears) + * Likes(Entity, Bananas) + */ + +#ifdef FLECS_PLECS + +#define FLECS_PARSER + +#ifndef FLECS_PLECS_H +#define FLECS_PLECS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Parse plecs string. + * This parses a plecs string and instantiates the entities in the world. + * + * @param world The world. + * @param name The script name (typically the file). + * @param str The plecs string. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *str); + +/** Parse plecs file. + * This parses a plecs file and instantiates the entities in the world. This + * operation is equivalent to loading the file contents and passing it to + * ecs_plecs_from_str. + * + * @param world The world. + * @param file The plecs file name. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_plecs_from_file( + ecs_world_t *world, + const char *filename); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/queue.h b/fggl/ecs2/flecs/include/flecs/addons/queue.h new file mode 100644 index 0000000000000000000000000000000000000000..327ad4e83b29ba927357f69e0aeb798aaa531dcf --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/queue.h @@ -0,0 +1,88 @@ +/** + * @file queue.h + * @brief Queue datastructure. + * + * The queue data structure implements a fixed-size ringbuffer. It is not used + * by the flecs core, but is used by flecs-hub modules. + */ + +#ifdef FLECS_QUEUE + +#ifndef FLECS_QUEUE_H_ +#define FLECS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_queue_t ecs_queue_t; + +FLECS_API +ecs_queue_t* _ecs_queue_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_queue_new(T, elem_count)\ + _ecs_queue_new(ECS_VECTOR_T(T), elem_count) + +FLECS_API +ecs_queue_t* _ecs_queue_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array); + +#define ecs_queue_from_array(T, elem_count, array)\ + _ecs_queue_from_array(ECS_VECTOR_T(T), elem_count, array) + +FLECS_API +void* _ecs_queue_push( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_queue_push(queue, T)\ + (T*)_ecs_queue_push(queue, ECS_VECTOR_T(T)) + +FLECS_API +void* _ecs_queue_get( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_queue_get(queue, T, index)\ + (T*)_ecs_queue_get(queue, ECS_VECTOR_T(T), index) + +#define ecs_queue_get_t(vector, size, alignment, index) \ + _ecs_queue_get(vector, ECS_VECTOR_U(size, alignment), index) + +FLECS_API +void* _ecs_queue_last( + ecs_queue_t *queue, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_queue_last(queue, T)\ + (T*)_ecs_queue_last(queue, ECS_VECTOR_T(T)) + +FLECS_API +int32_t ecs_queue_index( + ecs_queue_t *queue); + +FLECS_API +int32_t ecs_queue_count( + ecs_queue_t *queue); + +FLECS_API +void ecs_queue_free( + ecs_queue_t *queue); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/snapshot.h b/fggl/ecs2/flecs/include/flecs/addons/snapshot.h new file mode 100644 index 0000000000000000000000000000000000000000..5fb9bd2eb83d45d54e10a0b48f03d64a263e479f --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/snapshot.h @@ -0,0 +1,103 @@ +/** + * @file snapshot.h + * @brief Snapshot addon. + * + * A snapshot records the state of a world in a way so that it can be restored + * later. Snapshots work with POD components and non-POD components, provided + * that the appropriate lifecycle actions are registered for non-POD components. + * + * A snapshot is tightly coupled to a world. It is not possible to restore a + * snapshot from world A into world B. + */ + +#ifdef FLECS_SNAPSHOT + +#ifndef FLECS_SNAPSHOT_H +#define FLECS_SNAPSHOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** A snapshot stores the state of a world in a particular point in time. */ +typedef struct ecs_snapshot_t ecs_snapshot_t; + +/** Create a snapshot. + * This operation makes a copy of all component in the world that matches the + * specified filter. + * + * @param world The world to snapshot. + * @param return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *world); + +/** Create a filtered snapshot. + * This operation is the same as ecs_snapshot_take, but accepts an iterator so + * an application can control what is stored by the snapshot. + * + * @param iter An iterator to the data to be stored by the snapshot. + * @param next A function pointer to the next operation for the iterator. + * @param return The snapshot. + */ +FLECS_API +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t action); + +/** Restore a snapshot. + * This operation restores the world to the state it was in when the specified + * snapshot was taken. A snapshot can only be used once for restoring, as its + * data replaces the data that is currently in the world. + * This operation also resets the last issued entity handle, so any calls to + * ecs_new may return entity ids that have been issued before restoring the + * snapshot. + * + * The world in which the snapshot is restored must be the same as the world in + * which the snapshot is taken. + * + * @param world The world to restore the snapshot to. + * @param snapshot The snapshot to restore. + */ +FLECS_API +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot); + +/** Obtain iterator to snapshot data. + * + * @param snapshot The snapshot to iterate over. + * @return Iterator to snapshot data. */ +FLECS_API +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot, + const ecs_filter_t *filter); + +/** Progress snapshot iterator. + * + * @param iter The snapshot iterator. + * @return True if more data is available, otherwise false. + */ +FLECS_API +bool ecs_snapshot_next( + ecs_iter_t *iter); + + +/** Free snapshot resources. + * This frees resources associated with a snapshot without restoring it. + * + * @param world The world. + * @param snapshot The snapshot to free. + */ +FLECS_API +void ecs_snapshot_free( + ecs_snapshot_t *snapshot); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/addons/stats.h b/fggl/ecs2/flecs/include/flecs/addons/stats.h new file mode 100644 index 0000000000000000000000000000000000000000..2864890d8a3486cd36c49b77ea2d662d1e87bffe --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/addons/stats.h @@ -0,0 +1,190 @@ +/** + * @file stats.h + * @brief Statistics addon. + * + * The statistics addon enables an application to obtain detailed metrics about + * the storage, systems and operations of a world. + */ + +#ifdef FLECS_STATS + +#ifndef FLECS_STATS_H +#define FLECS_STATS_H + +#ifdef FLECS_SYSTEM +#include "../modules/system.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_STAT_WINDOW (60) + +/** Simple value that indicates current state */ +typedef struct ecs_gauge_t { + float avg[ECS_STAT_WINDOW]; + float min[ECS_STAT_WINDOW]; + float max[ECS_STAT_WINDOW]; +} ecs_gauge_t; + +/* Monotonically increasing counter */ +typedef struct ecs_counter_t { + ecs_gauge_t rate; /**< Keep track of deltas too */ + float value[ECS_STAT_WINDOW]; +} ecs_counter_t; + +typedef struct ecs_world_stats_t { + /* Allows struct to be initialized with {0} */ + int32_t dummy_; + + ecs_gauge_t entity_count; /**< Number of entities */ + ecs_gauge_t component_count; /**< Number of components */ + ecs_gauge_t query_count; /**< Number of queries */ + ecs_gauge_t system_count; /**< Number of systems */ + ecs_gauge_t table_count; /**< Number of tables */ + ecs_gauge_t empty_table_count; /**< Number of empty tables */ + ecs_gauge_t singleton_table_count; /**< Number of singleton tables. Singleton tables are tables with just a single entity that contains itself */ + ecs_gauge_t matched_entity_count; /**< Number of entities matched by queries */ + ecs_gauge_t matched_table_count; /**< Number of tables matched by queries */ + + /* Deferred operations */ + ecs_counter_t new_count; + ecs_counter_t bulk_new_count; + ecs_counter_t delete_count; + ecs_counter_t clear_count; + ecs_counter_t add_count; + ecs_counter_t remove_count; + ecs_counter_t set_count; + ecs_counter_t discard_count; + + /* Timing */ + ecs_counter_t world_time_total_raw; /**< Actual time passed since simulation start (first time progress() is called) */ + ecs_counter_t world_time_total; /**< Simulation time passed since simulation start. Takes into account time scaling */ + ecs_counter_t frame_time_total; /**< Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_counter_t system_time_total; /**< Time spent on processing systems. */ + ecs_counter_t merge_time_total; /**< Time spent on merging deferred actions. */ + ecs_gauge_t fps; /**< Frames per second. */ + ecs_gauge_t delta_time; /**< Delta_time. */ + + /* Frame data */ + ecs_counter_t frame_count_total; /**< Number of frames processed. */ + ecs_counter_t merge_count_total; /**< Number of merges executed. */ + ecs_counter_t pipeline_build_count_total; /**< Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_counter_t systems_ran_frame; /**< Number of systems ran in the last frame. */ + + /** Current position in ringbuffer */ + int32_t t; +} ecs_world_stats_t; + +/* Statistics for a single query (use ecs_get_query_stats) */ +typedef struct ecs_query_stats_t { + ecs_gauge_t matched_table_count; /**< Number of matched non-empty tables. This is the number of tables + * iterated over when evaluating a query. */ + + ecs_gauge_t matched_empty_table_count; /**< Number of matched empty tables. Empty tables are not iterated over when + * evaluating a query. */ + + ecs_gauge_t matched_entity_count; /**< Number of matched entities across all tables */ + + /** Current position in ringbuffer */ + int32_t t; +} ecs_query_stats_t; + +/** Statistics for a single system (use ecs_get_system_stats) */ +typedef struct ecs_system_stats_t { + ecs_query_stats_t query_stats; + ecs_counter_t time_spent; /**< Time spent processing a system */ + ecs_counter_t invoke_count; /**< Number of times system is invoked */ + ecs_gauge_t active; /**< Whether system is active (is matched with >0 entities) */ + ecs_gauge_t enabled; /**< Whether system is enabled */ +} ecs_system_stats_t; + +/** Statistics for all systems in a pipeline. */ +typedef struct ecs_pipeline_stats_t { + /** Vector with system ids of all systems in the pipeline. The systems are + * stored in the order they are executed. Merges are represented by a 0. */ + ecs_vector_t *systems; + + /** Map with system statistics. For each system in the systems vector, an + * entry in the map exists of type ecs_system_stats_t. */ + ecs_map_t *system_stats; +} ecs_pipeline_stats_t; + +/** Get world statistics. + * Obtain statistics for the provided world. This operation loops several times + * over the tables in the world, and can impact application performance. + * + * @param world The world. + * @param stats Out parameter for statistics. + */ +FLECS_API void ecs_get_world_stats( + const ecs_world_t *world, + ecs_world_stats_t *stats); + +/** Print world statistics. + * Print statistics obtained by ecs_get_world_statistics and in the + * ecs_world_info_t struct. + * + * @param world The world. + * @param stats The statistics to print. + */ +FLECS_API void ecs_dump_world_stats( + 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_get_query_stats( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s); + +#ifdef FLECS_SYSTEM +/** 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_get_system_stats( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *stats); +#endif + +#ifdef FLECS_PIPELINE +/** Get pipeline statistics. + * Obtain statistics for the provided pipeline. + * + * @param world The world. + * @param pipeline The pipeline. + * @param stats Out parameter for statistics. + * @return true if success, false if not a pipeline. + */ +FLECS_API bool ecs_get_pipeline_stats( + const ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *stats); +#endif + +FLECS_API void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/bake_config.h b/fggl/ecs2/flecs/include/flecs/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..4bf0354a75134176ff0725e4fbe73e47d56b0ee5 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/bake_config.h @@ -0,0 +1,39 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 flecs_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) + #define FLECS_API __declspec(dllexport) +#elif 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 + diff --git a/fggl/ecs2/flecs/include/flecs/cpp/builder.hpp b/fggl/ecs2/flecs/include/flecs/cpp/builder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..82404fece3fe3ce0f3e694708f59a2993411541e --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/builder.hpp @@ -0,0 +1,1079 @@ + +namespace flecs { + +template<typename Base> +class term_id_builder_i { +public: + term_id_builder_i() : m_term_id(nullptr) { } + + virtual ~term_id_builder_i() { } + + template<typename T> + Base& entity() { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + m_term_id->entity = _::cpp_type<T>::id(world()); + return *this; + } + + Base& entity(flecs::id_t id) { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + m_term_id->entity = id; + return *this; + } + + Base& name(const char *name) { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + // Const cast is safe, when the value is actually used to construct a + // query, it will be duplicated. + m_term_id->name = const_cast<char*>(name); + return *this; + } + + Base& var(flecs::var_kind_t var = flecs::VarIsVariable) { + m_term_id->var = static_cast<ecs_var_kind_t>(var); + return *this; + } + + Base& var(const char *name) { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + // Const cast is safe, when the value is actually used to construct a + // query, it will be duplicated. + m_term_id->name = const_cast<char*>(name); + return var(); // Default to VarIsVariable + } + + Base& set(uint8_t mask, const flecs::id_t relation = flecs::IsA) + { + ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); + m_term_id->set.mask = mask; + m_term_id->set.relation = relation; + return *this; + } + + Base& superset(const flecs::id_t relation = flecs::IsA, uint8_t mask = 0) + { + ecs_assert(!(mask & flecs::SubSet), ECS_INVALID_PARAMETER, NULL); + return set(flecs::SuperSet | mask, relation); + } + + Base& subset(const flecs::id_t relation = flecs::IsA, uint8_t mask = 0) + { + ecs_assert(!(mask & flecs::SuperSet), ECS_INVALID_PARAMETER, NULL); + return set(flecs::SubSet | mask, relation); + } + + Base& min_depth(int32_t min_depth) { + m_term_id->set.min_depth = min_depth; + return *this; + } + + Base& max_depth(int32_t max_depth) { + m_term_id->set.max_depth = max_depth; + return *this; + } + + ecs_term_id_t *m_term_id; + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } +}; + +template<typename Base> +class term_builder_i : public term_id_builder_i<Base> { +public: + term_builder_i() : m_term(nullptr) { } + + term_builder_i(ecs_term_t *term_ptr) { + set_term(term_ptr); + } + + template<typename T> + Base& id() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = _::cpp_type<T>::id(world()); + return *this; + } + + template<typename R, typename O> + Base& id() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = _::cpp_type<R>::id(world()); + m_term->args[1].entity = _::cpp_type<O>::id(world()); + return *this; + } + + template<typename R> + Base& id(id_t o) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = _::cpp_type<R>::id(world()); + m_term->args[1].entity = o; + return *this; + } + + Base& id(id_t id) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = id; + return *this; + } + + Base& id(const flecs::type& type); + + Base& id(id_t r, id_t o) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = r; + m_term->args[1].entity = o; + return *this; + } + + Base& expr(const char *expr) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + const char *ptr; + if ((ptr = ecs_parse_term(world(), nullptr, expr, expr, m_term)) == nullptr) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + // Should not have more than one term + ecs_assert(ptr[0] == 0, ECS_INVALID_PARAMETER, NULL); + return *this; + } + + Base& predicate() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + this->m_term_id = &m_term->pred; + return *this; + } + + Base& subject() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + this->m_term_id = &m_term->args[0]; + return *this; + } + + Base& object() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + this->m_term_id = &m_term->args[1]; + return *this; + } + + Base& subject(entity_t entity) { + this->subject(); + this->m_term_id->entity = entity; + return *this; + } + + Base& object(entity_t entity) { + this->object(); + this->m_term_id->entity = entity; + return *this; + } + + template<typename T> + Base& subject() { + this->subject(); + this->m_term_id->entity = _::cpp_type<T>::id(world()); + return *this; + } + + template<typename T> + Base& object() { + this->object(); + this->m_term_id->entity = _::cpp_type<T>::id(world()); + return *this; + } + + Base& role(id_t role) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->role = role; + return *this; + } + + Base& inout(flecs::inout_kind_t inout) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->inout = static_cast<ecs_inout_kind_t>(inout); + return *this; + } + + Base& oper(flecs::oper_kind_t oper) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->oper = static_cast<ecs_oper_kind_t>(oper); + return *this; + } + + Base& singleton() { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_term->id || m_term->pred.entity, ECS_INVALID_PARAMETER, NULL); + + flecs::id_t pred = m_term->id; + if (!pred) { + pred = m_term->pred.entity; + } + + ecs_assert(pred != 0, ECS_INVALID_PARAMETER, NULL); + + m_term->args[0].entity = pred; + + return *this; + } + + flecs::id id() { + return flecs::id(world(), m_term->id); + } + + flecs::entity get_subject() { + return flecs::entity(world(), m_term->args[0].entity); + } + + flecs::entity get_object() { + return flecs::entity(world(), m_term->args[1].entity); + } + + flecs::inout_kind_t inout() { + return static_cast<flecs::inout_kind_t>(m_term->inout); + } + + flecs::oper_kind_t oper() { + return static_cast<flecs::oper_kind_t>(m_term->oper); + } + + ecs_term_t *m_term; + +protected: + virtual flecs::world_t* world() = 0; + + void set_term(ecs_term_t *term) { + m_term = term; + if (term) { + this->m_term_id = &m_term->args[0]; // default to subject + } else { + this->m_term_id = nullptr; + } + } + +private: + operator Base&() { + return *static_cast<Base*>(this); + } +}; + +// Class that describes a term +class term final : public term_builder_i<term> { +public: + term(flecs::world_t *world_ptr) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { value.move = true; } + + term(flecs::world_t *world_ptr, id_t id) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { + value.move = true; + this->id(id); + } + + term(flecs::world_t *world_ptr, ecs_term_t t) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { + value = t; + value.move = false; + this->set_term(&value); + } + + term(flecs::world_t *world_ptr, id_t r, id_t o) + : term_builder_i<term>(&value) + , value({}) + , m_world(world_ptr) { + value.move = true; + this->id(r, o); + } + + term(const term& obj) : term_builder_i<term>(&value) { + m_world = obj.m_world; + value = ecs_term_copy(&obj.value); + this->set_term(&value); + } + + term(term&& obj) : term_builder_i<term>(&value) { + m_world = obj.m_world; + value = ecs_term_move(&obj.value); + obj.reset(); + this->set_term(&value); + } + + term& operator=(const term& obj) { + ecs_assert(m_world == obj.m_world, ECS_INVALID_PARAMETER, NULL); + ecs_term_fini(&value); + value = ecs_term_copy(&obj.value); + this->set_term(&value); + return *this; + } + + term& operator=(term&& obj) { + ecs_assert(m_world == obj.m_world, ECS_INVALID_PARAMETER, NULL); + ecs_term_fini(&value); + value = obj.value; + this->set_term(&value); + obj.reset(); + return *this; + } + + ~term() { + ecs_term_fini(&value); + } + + void reset() { + value = {}; + this->set_term(nullptr); + } + + int finalize() { + return ecs_term_finalize(m_world, nullptr, nullptr, &value); + } + + bool is_set() { + return ecs_term_is_initialized(&value); + } + + bool is_trivial() { + return ecs_term_is_trivial(&value); + } + + ecs_term_t move() { /* explicit move to ecs_term_t */ + return ecs_term_move(&value); + } + + ecs_term_t value; + +protected: + flecs::world_t* world() override { return m_world; } + +private: + flecs::world_t *m_world; +}; + +// Filter builder interface +template<typename Base, typename ... Components> +class filter_builder_i : public term_builder_i<Base> { +public: + filter_builder_i(ecs_filter_desc_t *desc, int32_t term_index = 0) + : m_term_index(term_index) + , m_desc(desc) { } + + Base& expr(const char *expr) { + m_desc->expr = expr; + return *this; + } + + Base& substitute_default(bool value = true) { + m_desc->substitute_default = value; + return *this; + } + + Base& term() { + if (m_term_index >= ECS_TERM_DESC_CACHE_SIZE) { + if (m_term_index == ECS_TERM_DESC_CACHE_SIZE) { + m_desc->terms_buffer = ecs_os_calloc_n( + ecs_term_t, m_term_index + 1); + ecs_os_memcpy_n(m_desc->terms_buffer, m_desc->terms, + ecs_term_t, m_term_index); + ecs_os_memset_n(m_desc->terms, 0, + ecs_term_t, ECS_TERM_DESC_CACHE_SIZE); + } else { + m_desc->terms_buffer = ecs_os_realloc_n(m_desc->terms_buffer, + ecs_term_t, m_term_index + 1); + } + + m_desc->terms_buffer_count = m_term_index + 1; + + this->set_term(&m_desc->terms_buffer[m_term_index]); + } else { + this->set_term(&m_desc->terms[m_term_index]); + } + + m_term_index ++; + return *this; + } + + Base& arg(int32_t term_index) { + ecs_assert(term_index > 0, ECS_INVALID_PARAMETER, NULL); + int32_t prev_index = m_term_index; + m_term_index = term_index - 1; + this->term(); + m_term_index = prev_index; + ecs_assert(ecs_term_is_initialized(this->m_term), ECS_INVALID_PARAMETER, NULL); + return *this; + } + + template<typename T> + Base& term() { + this->term(); + *this->m_term = flecs::term(world()).id<T>().move(); + return *this; + } + + Base& term(id_t id) { + this->term(); + *this->m_term = flecs::term(world()).id(id).move(); + return *this; + } + + template<typename R, typename O> + Base& term() { + this->term(); + *this->m_term = flecs::term(world()).id<R, O>().move(); + return *this; + } + + template<typename R> + Base& term(id_t o) { + this->term(); + *this->m_term = flecs::term(world()).id<R>(o).move(); + return *this; + } + + Base& term(id_t r, id_t o) { + this->term(); + *this->m_term = flecs::term(world()).id(r, o).move(); + return *this; + } + + Base& term(const flecs::type& type) { + this->term(); + *this->m_term = flecs::term(world()).id(type).move(); + return *this; + } + + Base& term(const char *expr) { + this->term(); + *this->m_term = flecs::term(world()).expr(expr).move(); + return *this; + } + + Base& term(flecs::term& term) { + this->term(); + *this->m_term = term.move(); + return *this; + } + + Base& term(flecs::term&& term) { + this->term(); + *this->m_term = term.move(); + return *this; + } + + void populate_filter_from_pack() { + flecs::array<flecs::id_t, sizeof...(Components)> ids ({ + (_::cpp_type<Components>::id(world()))... + }); + + flecs::array<flecs::inout_kind_t, sizeof...(Components)> inout_kinds ({ + (type_to_inout<Components>())... + }); + + flecs::array<flecs::oper_kind_t, sizeof...(Components)> oper_kinds ({ + (type_to_oper<Components>())... + }); + + size_t i = 0; + for (auto id : ids) { + this->term(id).inout(inout_kinds[i]).oper(oper_kinds[i]); + i ++; + } + } + +protected: + virtual flecs::world_t* world() = 0; + int32_t m_term_index; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + template <typename T, if_t< is_const<T>::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() const { + return flecs::In; + } + + template <typename T, if_t< is_reference<T>::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() const { + return flecs::Out; + } + + template <typename T, if_not_t< + is_const<T>::value || is_reference<T>::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() const { + return flecs::InOutDefault; + } + + template <typename T, if_t< is_pointer<T>::value > = 0> + constexpr flecs::oper_kind_t type_to_oper() const { + return flecs::Optional; + } + + template <typename T, if_not_t< is_pointer<T>::value > = 0> + constexpr flecs::oper_kind_t type_to_oper() const { + return flecs::And; + } + + ecs_filter_desc_t *m_desc; +}; + +// Query builder interface +template<typename Base, typename ... Components> +class query_builder_i : public filter_builder_i<Base, Components ...> { + using BaseClass = filter_builder_i<Base, Components ...>; +public: + query_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) { } + + query_builder_i(ecs_query_desc_t *desc, int32_t term_index = 0) + : BaseClass(&desc->filter, term_index) + , m_desc(desc) { } + + /** 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 (sub)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * 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 <typename T> + Base& order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_order_by_action_t cmp = reinterpret_cast<ecs_order_by_action_t>(compare); + return this->order_by(_::cpp_type<T>::id(world()), cmp); + } + + /** Sort the output of a query. + * Same as order_by<T>, 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*)) { + m_desc->order_by = reinterpret_cast<ecs_order_by_action_t>(compare); + m_desc->order_by_component = component; + return *this; + } + + /** Group and sort matched tables. + * Similar yo ecs_query_order_by, but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * 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 rank The rank action. + */ + template <typename T> + Base& group_by(int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + ecs_group_by_action_t rnk = reinterpret_cast<ecs_group_by_action_t>(rank); + return this->group_by(_::cpp_type<T>::id(this->m_world), rnk); + } + + /** Group and sort matched tables. + * Same as group_by<T>, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param rank The rank action. + */ + Base& group_by(flecs::entity_t component, int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + m_desc->group_by = reinterpret_cast<ecs_group_by_action_t>(rank); + m_desc->group_by_id = component; + return *this; + } + + /** Specify parent query (creates subquery) */ + Base& parent(const query_base& parent); + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + ecs_query_desc_t *m_desc; +}; + +// System builder interface +template<typename Base, typename ... Components> +class system_builder_i : public query_builder_i<Base, Components ...> { + using BaseClass = query_builder_i<Base, Components ...>; +public: + system_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) + , m_add_count(0) { } + + system_builder_i(ecs_system_desc_t *desc) + : BaseClass(&desc->query) + , m_desc(desc) + , m_add_count(0) { } + + /** Specify string-based signature. */ + Base& signature(const char *signature) { + m_desc->query.filter.expr = signature; + return *this; + } + + /** Specify when the system should be ran. + * Use this function to set in which phase the system should run or whether + * the system is reactive. Valid values for reactive systems are: + * + * flecs::OnAdd + * flecs::OnRemove + * flecs::OnSet + * flecs::UnSet + * + * @param kind The kind that specifies when the system should be ran. + */ + Base& kind(entity_t kind) { + m_desc->entity.add[0] = kind; + 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(FLECS_FLOAT interval) { + m_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) { + m_desc->rate = rate; + m_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) { + m_desc->rate = rate; + return *this; + } + + /** System is an on demand system */ + Base& on_demand() { + m_desc->entity.add[m_add_count ++] = flecs::OnDemand; + return *this; + } + + /** System is a hidden system */ + Base& hidden() { + m_desc->entity.add[m_add_count ++] = flecs::Hidden; + return *this; + } + + /** Associate system with entity */ + Base& self(flecs::entity self) { + m_desc->self = self; + return *this; + } + + /** Set system context */ + Base& ctx(void *ptr) { + m_desc->ctx = ptr; + return *this; + } + + ECS_DEPRECATED("use interval") + Base& period(FLECS_FLOAT period) { + return this->interval(period); + } + + ECS_DEPRECATED("use ctx") + Base& set_context(void *ptr) { + ctx(ptr); + return *this; + } + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + ecs_system_desc_t *m_desc; + int32_t m_add_count; +}; + +// Observer builder interface +template<typename Base, typename ... Components> +class observer_builder_i : public filter_builder_i<Base, Components ...> { + using BaseClass = filter_builder_i<Base, Components ...>; +public: + observer_builder_i() + : BaseClass(nullptr) + , m_desc(nullptr) + , m_event_count(0) { } + + observer_builder_i(ecs_observer_desc_t *desc) + : BaseClass(&desc->filter) + , m_desc(desc) + , m_event_count(0) { } + + /** Specify when the system should be ran. + * Use this function to set in which phase the system should run or whether + * the system is reactive. Valid values for reactive systems are: + * + * flecs::OnAdd + * flecs::OnRemove + * flecs::OnSet + * flecs::UnSet + * + * @param kind The kind that specifies when the system should be ran. + */ + Base& event(entity_t kind) { + m_desc->events[m_event_count ++] = kind; + return *this; + } + + /** Associate observer with entity */ + Base& self(flecs::entity self) { + m_desc->self = self; + return *this; + } + + /** Set system context */ + Base& ctx(void *ptr) { + m_desc->ctx = ptr; + return *this; + } + +protected: + virtual flecs::world_t* world() = 0; + +private: + operator Base&() { + return *static_cast<Base*>(this); + } + + ecs_observer_desc_t *m_desc; + int32_t m_event_count; +}; + +// Filter builder +template<typename ... Components> +class filter_builder_base + : public filter_builder_i<filter_builder_base<Components...>, Components ...> +{ +public: + filter_builder_base(flecs::world_t *world) + : filter_builder_i<filter_builder_base<Components...>, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + this->populate_filter_from_pack(); + } + + filter_builder_base(const filter_builder_base& obj) + : filter_builder_i<filter_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + filter_builder_base(filter_builder_base&& obj) + : filter_builder_i<filter_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + operator filter<Components ...>() const; + + operator ecs_filter_t() const { + ecs_filter_t f; + + int res = ecs_filter_init(this->m_world, &f, &this->m_desc); + if (res != 0) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (this->m_desc.terms_buffer) { + ecs_os_free(this->m_desc.terms_buffer); + } + + return f; + } + + filter<Components ...> build() const; + + ecs_filter_desc_t m_desc; + + flecs::world_t* world() override { return m_world; } + +protected: + flecs::world_t *m_world; +}; + +template<typename ... Components> +class filter_builder final : public filter_builder_base<Components...> { +public: + filter_builder(flecs::world_t *world) + : filter_builder_base<Components ...>(world) { } + + operator filter<>() const; +}; + +// Query builder +template<typename ... Components> +class query_builder_base + : public query_builder_i<query_builder_base<Components...>, Components ...> +{ +public: + query_builder_base(flecs::world_t *world) + : query_builder_i<query_builder_base<Components...>, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + this->populate_filter_from_pack(); + } + + query_builder_base(const query_builder_base& obj) + : query_builder_i<query_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + query_builder_base(query_builder_base&& obj) + : query_builder_i<query_builder_base<Components...>, Components ...>(&m_desc, obj.m_term_index) + { + m_world = obj.m_world; + m_desc = obj.m_desc; + } + + operator query<Components ...>() const; + + operator ecs_query_t*() const { + ecs_query_t *result = ecs_query_init(this->m_world, &this->m_desc); + + if (!result) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + if (this->m_desc.filter.terms_buffer) { + ecs_os_free(m_desc.filter.terms_buffer); + } + + return result; + } + + query<Components ...> build() const; + + ecs_query_desc_t m_desc; + + flecs::world_t* world() override { return m_world; } + +protected: + flecs::world_t *m_world; +}; + +template<typename ... Components> +class query_builder final : public query_builder_base<Components...> { +public: + query_builder(flecs::world_t *world) + : query_builder_base<Components ...>(world) { } + + operator query<>() const; +}; + +template<typename ... Components> +class system_builder final + : public system_builder_i<system_builder<Components ...>, Components ...> +{ + using Class = system_builder<Components ...>; +public: + explicit system_builder(flecs::world_t* world, const char *name = nullptr, const char *expr = nullptr) + : system_builder_i<Class, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + m_desc.entity.name = name; + m_desc.entity.sep = "::"; + m_desc.entity.add[0] = flecs::OnUpdate; + m_desc.query.filter.expr = expr; + this->populate_filter_from_pack(); + } + + // put using outside of action so we can still use it without it being + // flagged as deprecated. + template <typename Func> + using action_invoker_t = typename _::iter_invoker< + typename std::decay<Func>::type, Components...>; + + template <typename Func> + ECS_DEPRECATED("use each or iter") + system<Components...> action(Func&& func) const; + + /* Iter (or each) is mandatory and always the last thing that + * is added in the fluent method chain. Create system signature from both + * template parameters and anything provided by the signature method. */ + template <typename Func> + system<Components...> iter(Func&& func) const; + + /* Each is similar to action, but accepts a function that operates on a + * single entity */ + template <typename Func> + system<Components...> each(Func&& func) const; + + ecs_system_desc_t m_desc; + +protected: + flecs::world_t* world() override { return m_world; } + flecs::world_t *m_world; + +private: + template <typename Invoker, typename Func> + entity_t build(Func&& func, bool is_each) const { + auto ctx = FLECS_NEW(Invoker)(std::forward<Func>(func)); + + entity_t e, kind = m_desc.entity.add[0]; + bool is_trigger = kind == flecs::OnAdd || kind == flecs::OnRemove; + + if (is_trigger) { + ecs_trigger_desc_t desc = {}; + ecs_term_t term = m_desc.query.filter.terms[0]; + if (ecs_term_is_initialized(&term)) { + desc.term = term; + } else { + desc.expr = m_desc.query.filter.expr; + } + + desc.entity.entity = m_desc.entity.entity; + desc.events[0] = kind; + desc.callback = Invoker::run; + desc.self = m_desc.self; + desc.ctx = m_desc.ctx; + desc.binding_ctx = ctx; + desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj<Invoker>); + + e = ecs_trigger_init(m_world, &desc); + } else { + ecs_system_desc_t desc = m_desc; + desc.callback = Invoker::run; + desc.self = m_desc.self; + desc.query.filter.substitute_default = is_each; + desc.binding_ctx = ctx; + desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj<Invoker>); + + e = ecs_system_init(m_world, &desc); + } + + if (this->m_desc.query.filter.terms_buffer) { + ecs_os_free(m_desc.query.filter.terms_buffer); + } + + return e; + } +}; + +template<typename ... Components> +class observer_builder final + : public observer_builder_i<observer_builder<Components ...>, Components ...> +{ + using Class = observer_builder<Components ...>; +public: + explicit observer_builder(flecs::world_t* world, const char *name = nullptr, const char *expr = nullptr) + : observer_builder_i<Class, Components ...>(&m_desc) + , m_desc({}) + , m_world(world) + { + m_desc.entity.name = name; + m_desc.entity.sep = "::"; + m_desc.entity.add[0] = flecs::OnUpdate; + m_desc.filter.expr = expr; + this->populate_filter_from_pack(); + } + + /* Iter (or each) is mandatory and always the last thing that + * is added in the fluent method chain. Create system signature from both + * template parameters and anything provided by the signature method. */ + template <typename Func> + observer<Components...> iter(Func&& func) const; + + /* Each is similar to action, but accepts a function that operates on a + * single entity */ + template <typename Func> + observer<Components...> each(Func&& func) const; + + ecs_observer_desc_t m_desc; + +protected: + flecs::world_t* world() override { return m_world; } + flecs::world_t *m_world; + +private: + template <typename Invoker, typename Func> + entity_t build(Func&& func, bool is_each) const { + auto ctx = FLECS_NEW(Invoker)(std::forward<Func>(func)); + + ecs_observer_desc_t desc = m_desc; + desc.callback = Invoker::run; + desc.filter.substitute_default = is_each; + desc.binding_ctx = ctx; + desc.binding_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj<Invoker>); + + ecs_entity_t result = ecs_observer_init(m_world, &desc); + + if (this->m_desc.filter.terms_buffer) { + ecs_os_free(m_desc.filter.terms_buffer); + } + + return result; + } +}; + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/component.hpp b/fggl/ecs2/flecs/include/flecs/cpp/component.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4e89716d4a593614fde26b704a7029e1001ac9c4 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/component.hpp @@ -0,0 +1,679 @@ +//////////////////////////////////////////////////////////////////////////////// +//// Register component, provide global access to component handles / metadata +//////////////////////////////////////////////////////////////////////////////// + +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. + // + struct name_util { + /* Remove parts from typename that aren't needed for component name */ + static void trim_name(char *typeName) { + ecs_size_t len = ecs_os_strlen(typeName); + + /* Remove 'const' */ + ecs_size_t const_len = ecs_os_strlen("const "); + if ((len > const_len) && !ecs_os_strncmp(typeName, "const ", const_len)) { + ecs_os_memmove(typeName, typeName + const_len, len - const_len); + typeName[len - const_len] = '\0'; + len -= const_len; + } + + /* Remove 'struct' */ + ecs_size_t struct_len = ecs_os_strlen("struct "); + if ((len > struct_len) && !ecs_os_strncmp(typeName, "struct ", struct_len)) { + ecs_os_memmove(typeName, typeName + struct_len, len - struct_len); + typeName[len - struct_len] = '\0'; + len -= struct_len; + } + + /* Remove 'class' */ + ecs_size_t class_len = ecs_os_strlen("class "); + if ((len > class_len) && !ecs_os_strncmp(typeName, "class ", class_len)) { + ecs_os_memmove(typeName, typeName + class_len, len - class_len); + typeName[len - class_len] = '\0'; + len -= class_len; + } + + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; + } + + /* Remove const at end of string */ + if (len > const_len) { + if (!ecs_os_strncmp(&typeName[len - const_len], " const", const_len)) { + typeName[len - const_len] = '\0'; + } + } + } + }; + +// Compiler-specific conversion from __PRETTY_FUNCTION__ to component name. +// This code uses a trick that instantiates a function for the component type. +// Then __PRETTY_FUNCTION__ is used to obtain the name of the function. Because +// the result of __PRETTY_FUNCTION__ is not standardized, there are different +// implementations for clang, gcc and msvc. Code that uses a different compiler +// needs to register component names explicitly. +#if defined(__clang__) + static const unsigned int FRONT_SIZE = sizeof("static const char* flecs::_::name_helper<") - 1u; + static const unsigned int BACK_SIZE = sizeof(">::name() [T = ]") - 1u; + + template <typename T> + struct name_helper + { + static const char* name(void) { + static const size_t size = (sizeof(__PRETTY_FUNCTION__) - FRONT_SIZE - BACK_SIZE) / 2 + 1u; + static char typeName[size + 6] = {}; + memcpy(typeName, __PRETTY_FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif defined(__GNUC__) + static const unsigned int FRONT_SIZE = sizeof("static const char* flecs::_::name_helper<T>::name() [with T = ") - 1u; + static const unsigned int BACK_SIZE = sizeof("]") - 1u; + + template <typename T> + struct name_helper + { + static const char* name(void) { + static const size_t size = sizeof(__PRETTY_FUNCTION__) - FRONT_SIZE - BACK_SIZE; + static char typeName[size + 6] = {}; + memcpy(typeName, __PRETTY_FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#elif defined(_WIN32) + static const unsigned int FRONT_SIZE = sizeof("flecs::_::name_helper<") - 1u; + static const unsigned int BACK_SIZE = sizeof(">::name") - 1u; + + template <typename T> + struct name_helper + { + static const char* name(void) { + static const size_t size = sizeof(__FUNCTION__) - FRONT_SIZE - BACK_SIZE; + static char typeName[size + 6] = {}; + memcpy(typeName, __FUNCTION__ + FRONT_SIZE, size - 1u); + name_util::trim_name(typeName); + return typeName; + } + }; +#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 <typename T> +struct symbol_helper +{ + static char* symbol(void) { + const char *name = name_helper<T>::name(); + + // Symbol is same as name, but with '::' replaced with '.' + char *ptr, *sym = ecs_os_strdup(name); + ecs_size_t i, len = ecs_os_strlen(sym); + ptr = sym; + for (i = 0, ptr = sym; i < len && *ptr; i ++, ptr ++) { + if (*ptr == ':') { + sym[i] = '.'; + ptr ++; + } else { + sym[i] = *ptr; + } + } + + sym[i] = '\0'; + + return sym; + } +}; + +// 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<typename T, enable_if_t< + std::is_trivial<T>::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<typename T, enable_if_t< + std::is_trivial<T>::value == false + >* = nullptr> +void register_lifecycle_actions( + ecs_world_t *world, + ecs_entity_t component) +{ + if (!ecs_component_has_actions(world, component)) { + EcsComponentLifecycle cl{}; + cl.ctor = ctor<T>(); + cl.dtor = dtor<T>(); + + cl.copy = copy<T>(); + cl.copy_ctor = copy_ctor<T>(); + cl.move = move<T>(); + cl.move_ctor = move_ctor<T>(); + + cl.ctor_move_dtor = ctor_move_dtor<T>(); + cl.move_dtor = move_dtor<T>(); + + ecs_set_component_actions_w_entity( world, component, &cl); + } +} + +// Class that manages component ids across worlds & binaries. +// The cpp_type class stores the component id for a C++ type in a static global +// variable that is shared between worlds. Whenever a component is used this +// class will check if it already has been registered (has the global id been +// set), and if not, register the component with the world. +// +// If the id has been set, the class will ensure it is known by the world. If it +// is not known the component has been registered by another world and will be +// registered with the world using the same id. If the id does exist, the class +// will register it as a component, and verify whether the input is consistent. +template <typename T> +class cpp_type_size { +public: + static size_t size(bool allow_tag) { + // C++ types that have no members still have a size. Use std::is_empty + // to check if the type is empty. If so, use 0 for the component size. + // + // If s_allow_tag is set to false, the size returned by C++ is used. + // This is useful in cases where class instances are still required, as + // is the case with module classes. + if (allow_tag && std::is_empty<T>::value) { + return 0; + } else { + return sizeof(T); + } + } + + static size_t alignment(bool allow_tag) { + if (size(allow_tag) == 0) { + return 0; + } else { + return alignof(T); + } + } +}; + +template <typename T> +class cpp_type_impl { +public: + // Initialize component identifier + static void init(world_t* world, entity_t entity, bool allow_tag = true) { + // If an identifier was already set, check for consistency + if (s_id) { + // If an identifier was registered, a name should've been registered + // as well. + ecs_assert(s_name.c_str() != nullptr, ECS_INTERNAL_ERROR, NULL); + + // A component cannot be registered using a different identifier. + ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, + _::name_helper<T>::name()); + + ecs_assert(allow_tag == s_allow_tag, ECS_INTERNAL_ERROR, 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. + char *path = ecs_get_fullpath(world, entity); + s_id = entity; + s_name = flecs::string(path); + s_allow_tag = allow_tag; + } + + // 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. + static const char* strip_module(world_t *world) { + const char *name = _::name_helper<T>::name(); + entity_t scope = ecs_get_scope(world); + if (!scope) { + return name; + } + + char *path = ecs_get_path_w_sep(world, 0, scope, "::", nullptr); + if (path) { + const char *ptr = strrchr(name, ':'); + ecs_assert(ptr != name, ECS_INTERNAL_ERROR, NULL); + if (ptr) { + ptr --; + ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); + ecs_size_t name_path_len = static_cast<ecs_size_t>(ptr - name); + if (name_path_len <= ecs_os_strlen(path)) { + if (!ecs_os_strncmp(name, path, name_path_len)) { + name = &name[name_path_len + 2]; + } + } + } + } + ecs_os_free(path); + + return name; + } + + // 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) + { + if (!s_id) { + // If no world was provided the component cannot be registered + ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); + s_allow_tag = allow_tag; + } else { + ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); + ecs_assert(s_allow_tag == allow_tag, ECS_INVALID_PARAMETER, 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))) { + if (!s_id) { + s_id = id; + } + + // One type can only be associated with a single type + ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); + + char *symbol = nullptr; + + // 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) { + symbol = symbol_helper<T>::symbol(); + if (!name) { + // If no name was provided, retrieve the name implicitly from + // the name_helper class. + name = strip_module(world); + } + } else { + // If an explicit id is provided but it has no name, inherit + // the name from the type. + if (!ecs_get_name(world, id)) { + name = strip_module(world); + } + } + + ecs_component_desc_t desc = {}; + desc.entity.entity = s_id; + desc.entity.name = name; + desc.entity.sep = "::"; + desc.entity.root_sep = "::"; + desc.entity.symbol = symbol; + desc.size = cpp_type_size<T>::size(allow_tag); + desc.alignment = cpp_type_size<T>::alignment(allow_tag); + + ecs_entity_t entity = ecs_component_init(world, &desc); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(symbol); + + init(world, s_id, allow_tag); + s_id = entity; + } + + // 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. + if (!s_id || (world && !ecs_exists(world, s_id))) { + ecs_entity_t prev_scope = 0; + ecs_id_t prev_with = 0; + + if (world) { + prev_scope = ecs_set_scope(world, 0); + prev_with = ecs_set_with(world, 0); + } + + // This will register a component id, but will not register + // lifecycle callbacks. + id_explicit(world, name, allow_tag); + + // Register lifecycle callbacks, but only if the component has a + // size. Components that don't have a size are tags, and tags don't + // require construction/destruction/copy/move's. */ + if (size()) { + register_lifecycle_actions<T>(world, s_id); + } + + if (prev_with) { + ecs_set_with(world, prev_with); + } + if (prev_scope) { + ecs_set_scope(world, prev_scope); + } + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Obtain a component name + static const char* name(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // If the id is set, the name should also have been set + return s_name.c_str(); + } + + // Obtain a component name, don't register lifecycle if the component hadn't + // been registered yet. While functionally the same could be achieved by + // first calling id_explicit() and then name(), this function ensures + // that the lifecycle callback templates are not instantiated. This allows + // some types (such as module classes) to be created without a default + // constructor. + static const char* name_no_lifecycle(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id_explicit(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // Return + return s_name.c_str(); + } + + // Return the type of a component. + // The type is a vector of component ids. This will return a type with just + // the current component id. + static type_t type(world_t *world = nullptr) { + // If no id has been registered yet, do it now. + if (!s_id) { + id(world); + } + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + // Create a type from the component id. + if (!s_type) { + s_type = ecs_type_from_id(world, s_id); + } + + ecs_assert(s_type != nullptr, ECS_INTERNAL_ERROR, NULL); + + return s_type; + } + + // Return the size of a component. + static size_t size() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return cpp_type_size<T>::size(s_allow_tag); + } + + // Return the alignment of a component. + static size_t alignment() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return cpp_type_size<T>::alignment(s_allow_tag); + } + + // Was the component already registered. + static bool registered() { + return s_id != 0; + } + + // 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_type = NULL; + s_name.clear(); + } + +private: + static entity_t s_id; + static type_t s_type; + static flecs::string s_name; + static flecs::string s_symbol; + static bool s_allow_tag; +}; + +// Global templated variables that hold component identifier and other info +template <typename T> entity_t cpp_type_impl<T>::s_id( 0 ); +template <typename T> type_t cpp_type_impl<T>::s_type( nullptr ); +template <typename T> flecs::string cpp_type_impl<T>::s_name; +template <typename T> bool cpp_type_impl<T>::s_allow_tag( true ); + +// Front facing class for implicitly registering a component & obtaining +// static component data + +// Regular type +template <typename T> +class cpp_type<T, if_not_t< is_pair<T>::value >> + : public cpp_type_impl<base_type_t<T>> { }; + +// Pair type +template <typename T> +class cpp_type<T, if_t< is_pair<T>::value >> +{ +public: + // Override id method to return id of pair + static id_t id(world_t *world = nullptr) { + return ecs_pair( + cpp_type< pair_relation_t<T> >::id(world), + cpp_type< pair_object_t<T> >::id(world)); + } +}; + +} // namespace _ + +//////////////////////////////////////////////////////////////////////////////// +//// Register a component with flecs +//////////////////////////////////////////////////////////////////////////////// + +/** Plain old datatype, no lifecycle actions are registered */ +template <typename T> +flecs::entity pod_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 = _::name_helper<T>::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 (_::cpp_type<T>::registered()) { + /* Obtain component id. Because the component is already registered, + * this operation does nothing besides returning the existing id */ + id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id); + + /* If entity has a name check if it matches */ + if (ecs_get_name(world, id) != nullptr) { + if (!implicit_name && id >= EcsFirstUserComponentId) { + char *path = ecs_get_path_w_sep( + world, 0, id, "::", nullptr); + ecs_assert(!strcmp(path, n), + ECS_INCONSISTENT_NAME, name); + ecs_os_free(path); + } + } else { + /* 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, n, "::", "::"); + } + + /* 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_component_desc_t desc = {}; + desc.entity.entity = id; + desc.size = _::cpp_type<T>::size(); + desc.alignment = _::cpp_type<T>::alignment(); + ecs_entity_t entity = ecs_component_init(world, &desc); + (void)entity; + + ecs_assert(entity == id, ECS_INTERNAL_ERROR, NULL); + + /* This functionality could have been put in id_explicit, but since + * this code happens when a component is registered, and the entire API + * calls id_explicit, this would add a lot of overhead to each call. + * This is why when using multiple worlds, components should be + * registered explicitly. */ + } else { + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name. Ensure component is + * looked up from root. */ + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + ecs_entity_t entity; + if (id) { + entity = id; + } else { + entity = ecs_lookup_path_w_sep(world, 0, n, "::", "::", false); + } + + 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 (entity) { + if (!id) { + const char *sym = ecs_get_symbol(world, entity); + ecs_assert(sym != NULL, ECS_INTERNAL_ERROR, NULL); + (void)sym; + + char *symbol = _::symbol_helper<T>::symbol(); + ecs_assert(!ecs_os_strcmp(sym, symbol), ECS_NAME_IN_USE, n); + ecs_os_free(symbol); + + /* If an existing id was provided, it's possible that this id was + * registered with another type. Make sure that in this case at + * least the component size/alignment matches. + * This allows applications to alias two different types to the same + * id, which enables things like redefining a C type in C++ by + * inheriting from it & adding utility functions etc. */ + } else { + const EcsComponent *comp = ecs_get(world, entity, EcsComponent); + if (comp) { + ecs_assert(comp->size == ECS_SIZEOF(T), + ECS_INVALID_COMPONENT_SIZE, NULL); + ecs_assert(comp->alignment == ECS_ALIGNOF(T), + ECS_INVALID_COMPONENT_ALIGNMENT, NULL); + } else { + /* If the existing id is not a component, no checking is + * needed. */ + } + } + + /* If no entity is found, lookup symbol to check if the component was + * registered under a different name. */ + } else { + char *symbol = _::symbol_helper<T>::symbol(); + entity = ecs_lookup_symbol(world, symbol, false); + ecs_assert(entity == 0, ECS_INCONSISTENT_COMPONENT_ID, symbol); + ecs_os_free(symbol); + } + + /* Register id as usual */ + id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id); + } + + return flecs::entity(world, id); +} + +/** Register component */ +template <typename T> +flecs::entity component(flecs::world_t *world, const char *name = nullptr) { + flecs::entity result = pod_component<T>(world, name); + + if (_::cpp_type<T>::size()) { + _::register_lifecycle_actions<T>(world, result); + } + + return result; +} + +/* Register component with existing entity id */ +template <typename T> +void component_for_id(flecs::world_t *world, flecs::id_t id) { + flecs::entity result = pod_component<T>(world, nullptr, true, id); + + ecs_assert(result.id() == id, ECS_INTERNAL_ERROR, NULL); + + if (_::cpp_type<T>::size()) { + _::register_lifecycle_actions<T>(world, result); + } +} + +ECS_DEPRECATED("API detects automatically whether type is trivial") +template <typename T> +flecs::entity relocatable_component(const flecs::world& world, const char *name = nullptr) { + flecs::entity result = pod_component<T>(world, name); + + _::register_lifecycle_actions<T>(world.c_ptr(), result.id()); + + return result; +} + +template <typename T> +flecs::entity_t type_id() { + return _::cpp_type<T>::id(); +} + +} // namespace flecs + diff --git a/fggl/ecs2/flecs/include/flecs/cpp/entity.hpp b/fggl/ecs2/flecs/include/flecs/cpp/entity.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3b85cd093cf43d6629cb5bc864756e73ec83cb45 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/entity.hpp @@ -0,0 +1,1500 @@ + +namespace flecs { + +template<typename T, typename Base> +class entity_builder_base { +public: + const Base& base() const { return *static_cast<const Base*>(this); } + flecs::world_t* base_world() const { return base().world(); } + flecs::entity_t base_id() const { return base().id(); } + operator const Base&() const { + return this->base(); + } +}; + +} + +#ifdef FLECS_DEPRECATED +#include "../addons/deprecated/entity.hpp" +#else +namespace flecs +{ +template <typename Base> +class entity_builder_deprecated { }; +class entity_deprecated { }; +} +#endif + +namespace flecs +{ + +/** Entity view class + * This class provides readonly access to entities. Using this class to store + * entities in components ensures valid handles, as this class will always store + * the actual world vs. a stage. The constructors of this class will never + * create a new entity. + * + * To obtain a mutable handle to the entity, use the "mut" function. + */ +class entity_view : public id { +public: + 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(const flecs::world& world, const entity_view& id) + : flecs::id( world.get_world(), id.id() ) { } + + /** Wrap an existing entity id. + * + * @param world Pointer to the world in which the entity is created. + * @param id The entity id. + */ + explicit entity_view(world_t *world, const entity_view& id) + : flecs::id( flecs::world(world).get_world(), id.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 m_id; + } + + /** Check is entity is valid. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_valid() const { + return m_world && ecs_is_valid(m_world, m_id); + } + + explicit operator bool() const { + return is_valid(); + } + + /** Check is entity is alive. + * + * @return True if the entity is alive, false otherwise. + */ + bool is_alive() const { + return m_world && ecs_is_alive(m_world, m_id); + } + + /** Return the entity name. + * + * @return The entity name, or an empty string if the entity has no name. + */ + flecs::string_view name() const { + return flecs::string_view(ecs_get_name(m_world, m_id)); + } + + /** Return the entity path. + * + * @return The hierarchical entity path, or an empty string if the entity + * has no name. + */ + flecs::string path(const char *sep = "::", const char *init_sep = "::") const { + char *path = ecs_get_path_w_sep(m_world, 0, m_id, sep, init_sep); + return flecs::string(path); + } + + bool enabled() { + return !ecs_has_entity(m_world, m_id, flecs::Disabled); + } + + /** Return the type. + * + * @return Returns the entity type. + */ + flecs::type type() const; + + /** Return type containing the entity. + * + * @return A type that contains only this entity. + */ + flecs::type to_type() const; + + /** Iterate (component) ids of an entity. + * The function parameter must match the following signature: + * void(*)(flecs::id id) + * + * @param func The function invoked for each id. + */ + template <typename Func> + void each(const Func& func) const; + + /** Iterate objects for a given relationship. + * This operation will return the object for all ids that match with the + * (rel, *) pattern. + * + * The function parameter must match the following signature: + * void(*)(flecs::entity object) + * + * @param rel The relationship for which to iterate the objects. + * @param func The function invoked for each object. + */ + template <typename Func> + void each(const flecs::entity_view& rel, const Func& func) const; + + /** Iterate objects for a given relationship. + * This operation will return the object for all ids that match with the + * (Rel, *) pattern. + * + * The function parameter must match the following signature: + * void(*)(flecs::entity object) + * + * @tparam Rel The relationship for which to iterate the objects. + * @param func The function invoked for each object. + */ + template <typename Rel, typename Func> + void each(const Func& func) const { + return each(_::cpp_type<Rel>::id(m_world), func); + } + + /** Find all (component) ids contained by an entity matching a pattern. + * This operation will return all ids that match the provided pattern. The + * pattern may contain wildcards by using the flecs::Wildcard constant: + * + * match(flecs::Wildcard, ...) + * Matches with all non-pair ids. + * + * match(world.pair(rel, flecs::Wildcard)) + * Matches all pair ids with relationship rel + * + * match(world.pair(flecs::Wildcard, obj)) + * Matches all pair ids with object obj + * + * The function parameter must match the following signature: + * void(*)(flecs::id id) + * + * @param pattern The pattern to use for matching. + * @param func The function invoked for each matching id. + */ + template <typename Func> + void match(flecs::id_t pattern, const Func& func) const; + + /** 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 <typename T, if_t< is_actual<T>::value > = 0> + const T* get() const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const T*>(ecs_get_id(m_world, m_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 <typename T, typename A = actual_type_t<T>, + if_not_t< is_actual<T>::value > = 0> + const A* get() const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<A>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const A*>(ecs_get_id(m_world, m_id, comp_id)); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam R the relation type. + * @tparam O the object type. + */ + template <typename R, typename O, typename P = pair<R, O>, + typename A = actual_type_t<P>, if_not_t< flecs::is_pair<R>::value > = 0> + const A* get() const { + return this->get<P>(); + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam R the relation type. + * @param object the object. + */ + template<typename R> + const R* get(const flecs::entity_view& object) const { + auto comp_id = _::cpp_type<R>::id(m_world); + ecs_assert(_::cpp_type<R>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const R*>( + ecs_get_id(m_world, m_id, ecs_pair(comp_id, object.id()))); + } + + /** Get component value (untyped). + * + * @param component The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + const void* get(const flecs::entity_view& component) const { + return ecs_get_id(m_world, m_id, component.id()); + } + + /** Get a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * relation nor the object part of the pair are components, the operation + * will fail. + * + * @param relation the relation. + * @param object the object. + */ + const void* get(const flecs::entity_view& relation, const flecs::entity_view& object) const { + return ecs_get_id(m_world, m_id, ecs_pair(relation.id(), object.id())); + } + + /** Get 1..N components. + * This operation accepts a callback with as arguments the components to + * retrieve. The callback will only be invoked when the entity has all + * the components. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. + * + * @param func The callback to invoke. + * @return True if the entity has all components, false if not. + */ + template <typename Func, if_t< is_callable<Func>::value > = 0> + bool get(const Func& func) const; + + /** Get the object part from a pair. + * This operation gets the value for a pair from the entity. The relation + * part of the pair should not be a component. + * + * @tparam O the object type. + * @param relation the relation. + */ + template<typename O> + const O* get_w_object(const flecs::entity_view& relation) const { + auto comp_id = _::cpp_type<O>::id(m_world); + ecs_assert(_::cpp_type<O>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<const O*>( + ecs_get_id(m_world, m_id, ecs_pair(relation.id(), comp_id))); + } + + /** Get the object part from a pair. + * This operation gets the value for a pair from the entity. The relation + * part of the pair should not be a component. + * + * @tparam R the relation type. + * @tparam O the object type. + */ + template<typename R, typename O> + const O* get_w_object() const { + return get<pair_object<R, O>>(); + } + + /** Get object for a given relation. + * This operation returns the object for a given relation. The optional + * index can be used to iterate through objects, in case the entity has + * multiple instances for the same relation. + * + * @param relation The relation for which to retrieve the object. + * @param index The index (0 for the first instance of the relation). + */ + flecs::entity get_object(flecs::entity_t relation, int32_t index = 0) const; + + /** Get parent from an entity. + * This operation retrieves the parent entity that has the specified + * component. If no parent with the specified component is found, an entity + * with id 0 is returned. If multiple parents have the specified component, + * the operation returns the first encountered one. + * + * @tparam T The component for which to find the parent. + * @return The parent entity. + */ + template <typename T> + flecs::entity get_parent(); + + flecs::entity get_parent(flecs::entity_view e); + + /** Lookup an entity by name. + * Lookup an entity in the scope of this entity. The provided path may + * contain double colons as scope separators, for example: "Foo::Bar". + * + * @param path The name of the entity to lookup. + * @return The found entity, or entity::null if no entity matched. + */ + flecs::entity lookup(const char *path) const; + + /** Check if entity has the provided type. + * + * @param entity The type pointer to check. + * @return True if the entity has the provided type, false otherwise. + */ + bool has(type_t type) const { + return ecs_has_type(m_world, m_id, type); + } + + /** Check if entity has the provided entity. + * + * @param entity The entity to check. + * @return True if the entity has the provided entity, false otherwise. + */ + bool has(flecs::id_t e) const { + return ecs_has_id(m_world, m_id, e); + } + + /** 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 <typename T> + bool has() const { + return ecs_has_id(m_world, m_id, _::cpp_type<T>::id(m_world)); + } + + /** Check if entity has the provided pair. + * + * @tparam Relation The relation type. + * @param Object The object type. + * @return True if the entity has the provided component, false otherwise. + */ + template <typename Relation, typename Object> + bool has() const { + return this->has<Relation>(_::cpp_type<Object>::id(m_world)); + } + + /** Check if entity has the provided pair. + * + * @tparam Relation The relation type. + * @param object The object. + * @return True if the entity has the provided component, false otherwise. + */ + template <typename Relation> + bool has(flecs::id_t object) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + return ecs_has_id(m_world, m_id, ecs_pair(comp_id, object)); + } + + /** Check if entity has the provided pair. + * + * @param relation The relation. + * @param object The object. + * @return True if the entity has the provided component, false otherwise. + */ + bool has(flecs::id_t relation, flecs::id_t object) const { + return ecs_has_id(m_world, m_id, ecs_pair(relation, object)); + } + + /** Check if entity has the provided pair. + * + * @tparam Object The object type. + * @param relation The relation. + * @return True if the entity has the provided component, false otherwise. + */ + template <typename Object> + bool has_w_object(flecs::id_t relation) const { + auto comp_id = _::cpp_type<Object>::id(m_world); + return ecs_has_id(m_world, m_id, ecs_pair(relation, comp_id)); + } + + /** Check if entity owns the provided type. + * An type is owned if it is not shared from a base entity. + * + * @param type The type to check. + * @return True if the entity owns the provided type, false otherwise. + */ + bool owns(type_t type) const { + return ecs_type_owns_type( + m_world, ecs_get_type(m_world, m_id), type, true); + } + + /** Check if entity owns the provided entity. + * An entity is owned if it is not shared from a base entity. + * + * @param entity 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_entity(m_world, m_id, e, true); + } + + /** Check if entity owns the provided pair. + * + * @tparam Relation The relation type. + * @param object The object. + * @return True if the entity owns the provided component, false otherwise. + */ + template <typename Relation> + bool owns(flecs::id_t object) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + return owns(ecs_pair(comp_id, object)); + } + + /** Check if entity owns the provided pair. + * + * @param relation The relation. + * @param object The object. + * @return True if the entity owns the provided component, false otherwise. + */ + bool owns(flecs::id_t relation, flecs::id_t object) const { + return owns(ecs_pair(relation, object)); + } + + /** 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 <typename T> + bool owns() const { + return owns(_::cpp_type<T>::id(m_world)); + } + + /** Check if entity has the provided switch. + * + * @param sw The switch to check. + * @return True if the entity has the provided switch, false otherwise. + */ + bool has_switch(const flecs::type& sw) const; + + template <typename T> + bool has_switch() const { + return ecs_has_entity(m_world, m_id, + flecs::Switch | _::cpp_type<T>::id(m_world)); + } + + /** Check if entity has the provided case. + * + * @param sw_case The case to check. + * @return True if the entity has the provided case, false otherwise. + */ + bool has_case(flecs::id_t sw_case) const { + return ecs_has_entity(m_world, m_id, flecs::Case | sw_case); + } + + template<typename T> + bool has_case() const { + return this->has_case(_::cpp_type<T>::id(m_world)); + } + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + flecs::entity get_case(flecs::id_t sw) const; + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + template<typename T> + flecs::entity get_case() const; + + /** Get case for switch. + * + * @param sw The switch for which to obtain the case. + * @return True if the entity has the provided case, false otherwise. + */ + flecs::entity get_case(const flecs::type& sw) const; + + /** Test if component is enabled. + * + * @tparam T The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + template<typename T> + bool is_enabled() { + return ecs_is_component_enabled_w_entity( + m_world, m_id, _::cpp_type<T>::id(m_world)); + } + + /** Test if component is enabled. + * + * @param entity The component to test. + * @return True if the component is enabled, false if it has been disabled. + */ + bool is_enabled(const flecs::entity_view& e) { + return ecs_is_component_enabled_w_entity( + m_world, m_id, e.id()); + } + + /** Get current delta time. + * Convenience function so system implementations can get delta_time, even + * if they are using the .each() function. + * + * @return Current delta_time. + */ + FLECS_FLOAT delta_time() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->delta_time; + } + + /** Return iterator to entity children. + * Enables depth-first iteration over entity children. + * + * @return Iterator to child entities. + */ + child_iterator children() 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 stage An created for the current 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 stage An created for the current stage. + * @return An entity handle that allows for mutations in the current stage. + */ + flecs::entity mut(const flecs::entity_view& e) const; + +private: + flecs::entity set_stage(world_t *stage); +}; + +/** Fluent API for chaining entity operations + * This class contains entity operations that can be chained. For example, by + * using this class, an entity can be created like this: + * + * flecs::entity e = flecs::entity(world) + * .add<Position>() + * .add<Velocity>(); + */ +struct entity_builder_tag { }; // Tag to prevent ambiguous base + +template <typename Base> +class entity_builder : public entity_builder_base<entity_builder_tag, Base> { +public: + /** 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 <typename T> + const Base& add() const { + flecs_static_assert(is_flecs_constructible<T>::value, + "cannot default construct type: add T::T() or use emplace<T>()"); + ecs_add_id(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world())); + return *this; + } + + /** Add an entity to an entity. + * Add an entity to the entity. This is typically used for tagging. + * + * @param entity The entity to add. + */ + const Base& add(entity_t entity) const { + ecs_add_id(this->base_world(), this->base_id(), entity); + return *this; + } + + /** Add a type to an entity. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type The type to add. + */ + const Base& add(const type& type) const; + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @param relation The relation id. + * @param object The object id. + */ + const Base& add(entity_t relation, entity_t object) const { + ecs_add_pair(this->base_world(), this->base_id(), relation, object); + return *this; + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @tparam R the relation type. + * @tparam O the object type. + */ + template<typename R, typename O> + const Base& add() const { + return this->add<R>(_::cpp_type<O>::id(this->base_world())); + } + + /** Add a pair. + * This operation adds a pair to the entity. + * + * @tparam R the relation type. + * @param object the object type. + */ + template<typename R> + const Base& add(entity_t object) const { + flecs_static_assert(is_flecs_constructible<R>::value, + "cannot default construct type: add T::T() or use emplace<T>()"); + return this->add(_::cpp_type<R>::id(this->base_world()), object); + } + + /** Shortcut for add(IsA. obj). + * + * @param object the object id. + */ + const Base& is_a(entity_t object) const { + return this->add(flecs::IsA, object); + } + + template <typename T> + const Base& is_a() const { + return this->add(flecs::IsA, _::cpp_type<T>::id(this->base_world())); + } + + /** Shortcut for add(ChildOf. obj). + * + * @param object the object id. + */ + const Base& child_of(entity_t object) const { + return this->add(flecs::ChildOf, object); + } + + /** Shortcut for add(ChildOf. obj). + * + * @param object the object id. + */ + template <typename T> + const Base& child_of() const { + return this->add(flecs::ChildOf, _::cpp_type<T>::id(this->base_world())); + } + + /** Add a pair with object type. + * This operation adds a pair to the entity. The relation part of the pair + * should not be a component. + * + * @param relation the relation type. + * @tparam O the object type. + */ + template<typename O> + const Base& add_w_object(entity_t relation) const { + flecs_static_assert(is_flecs_constructible<O>::value, + "cannot default construct type: add T::T() or use emplace<T>()"); + return this->add(relation, _::cpp_type<O>::id(this->base_world())); + } + + /** Remove a component from an entity. + * + * @tparam T the type of the component to remove. + */ + template <typename T> + const Base& remove() const { + ecs_remove_id(this->base_world(), this->base_id(), _::cpp_type<T>::id(this->base_world())); + return *this; + } + + /** Remove an entity from an entity. + * + * @param entity The entity to remove. + */ + const Base& remove(entity_t entity) const { + ecs_remove_id(this->base_world(), this->base_id(), entity); + return *this; + } + + /** Remove a type from an entity. + * A type is a vector of component ids. This operation adds all components + * in a single operation, and is a more efficient version of doing + * individual add operations. + * + * @param type the type to remove. + */ + const Base& remove(const type& type) const; + + /** Remove a pair. + * This operation removes a pair from the entity. + * + * @param relation The relation id. + * @param object The object id. + */ + const Base& remove(entity_t relation, entity_t object) const { + ecs_remove_pair(this->base_world(), this->base_id(), relation, object); + return *this; + } + + /** Removes a pair. + * This operation removes a pair from the entity. + * + * @tparam Relation the relation type. + * @tparam Object the object type. + */ + template<typename Relation, typename Object> + const Base& remove() const { + return this->remove<Relation>(_::cpp_type<Object>::id(this->base_world())); + } + + /** Remove a pair. + * This operation adds a pair to the entity. + * + * @tparam Relation the relation type. + * @param object the object type. + */ + template<typename Relation> + const Base& remove(entity_t object) const { + return this->remove(_::cpp_type<Relation>::id(this->base_world()), object); + } + + /** Removes a pair with object type. + * This operation removes a pair from the entity. + * + * @param relation the relation type. + * @tparam Object the object type. + */ + template<typename Object> + const Base& remove_w_object(entity_t relation) const { + return this->remove(relation, _::cpp_type<Object>::id(this->base_world())); + } + + /** Add owned flag for component (forces ownership when instantiating) + * + * @param entity The entity for which to add the OWNED flag + */ + const Base& add_owned(entity_t entity) const { + ecs_add_id(this->base_world(), this->base_id(), ECS_OWNED | entity); + return *this; + } + + /** Add owned flag for component (forces ownership when instantiating) + * + * @tparam T The component for which to add the OWNED flag + */ + template <typename T> + const Base& add_owned() const { + ecs_add_id(this->base_world(), this->base_id(), ECS_OWNED | _::cpp_type<T>::id(this->base_world())); + return *this; + } + + ECS_DEPRECATED("use add_owned(flecs::entity e)") + const Base& add_owned(const type& type) const; + + /** Set value, add owned flag. + * + * @tparam T The component to set and for which to add the OWNED flag + */ + template <typename T> + const Base& set_owned(T&& val) const { + this->add_owned<T>(); + this->set<T>(std::forward<T>(val)); + return *this; + } + + /** Add a switch to an entity by id. + * The switch entity must be a type, that is it must have the EcsType + * component. Entities created with flecs::type are valid here. + * + * @param sw The switch entity id to add. + */ + const Base& add_switch(entity_t sw) const { + ecs_add_id(this->base_world(), this->base_id(), ECS_SWITCH | sw); + return *this; + } + + /** Add a switch to an entity by C++ type. + * The C++ type must be associated with a switch type. + * + * @param sw The switch to add. + */ + template <typename T> + const Base& add_switch() const { + ecs_add_id(this->base_world(), this->base_id(), + ECS_SWITCH | _::cpp_type<T>::id()); + return *this; + } + + /** Add a switch to an entity. + * Any instance of flecs::type can be used as a switch. + * + * @param sw The switch to add. + */ + const Base& add_switch(const type& sw) const; + + /** Remove a switch from an entity by id. + * + * @param sw The switch entity id to remove. + */ + const Base& remove_switch(entity_t sw) const { + ecs_remove_id(this->base_world(), this->base_id(), ECS_SWITCH | sw); + return *this; + } + + /** Add a switch to an entity by C++ type. + * The C++ type must be associated with a switch type. + * + * @param sw The switch to add. + */ + template <typename T> + const Base& remove_switch() const { + ecs_remove_id(this->base_world(), this->base_id(), + ECS_SWITCH | _::cpp_type<T>::id()); + return *this; + } + + /** Remove a switch from an entity. + * Any instance of flecs::type can be used as a switch. + * + * @param sw The switch to remove. + */ + const Base& remove_switch(const type& sw) const; + + /** Add a switch to an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to add. + */ + const Base& add_case(entity_t sw_case) const { + ecs_add_id(this->base_world(), this->base_id(), ECS_CASE | sw_case); + return *this; + } + + /** Add a switch to an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @tparam T The case to add. + */ + template<typename T> + const Base& add_case() const { + return this->add_case(_::cpp_type<T>::id()); + } + + /** Remove a case from an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @param sw_case The case entity id to remove. + */ + const Base& remove_case(entity_t sw_case) const { + ecs_remove_id(this->base_world(), this->base_id(), ECS_CASE | sw_case); + return *this; + } + + /** Remove a switch from an entity by id. + * The case must belong to a switch that is already added to the entity. + * + * @tparam T The case to remove. + */ + template<typename T> + const Base& remove_case() const { + return this->remove_case(_::cpp_type<T>::id()); + } + + /** Enable an entity. + * Enabled entities are matched with systems and can be searched with + * queries. + */ + const Base& enable() const { + ecs_enable(this->base_world(), this->base_id(), true); + return *this; + } + + /** Disable an entity. + * Disabled entities are not matched with systems and cannot be searched + * with queries, unless explicitly specified in the query expression. + */ + const Base& disable() const { + ecs_enable(this->base_world(), this->base_id(), false); + return *this; + } + + /** Enable a component. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @tparam T The component to enable. + */ + template<typename T> + const Base& enable() const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), _::cpp_type<T>::id(), true); + return *this; + } + + /** Disable a component. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @tparam T The component to enable. + */ + template<typename T> + const Base& disable() const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), _::cpp_type<T>::id(), false); + return *this; + } + + /** Enable a component. + * See enable<T>. + * + * @param component The component to enable. + */ + const Base& enable(entity_t comp) const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), comp, true); + return *this; + } + + /** Disable a component. + * See disable<T>. + * + * @param component The component to disable. + */ + const Base& disable(entity_t comp) const { + ecs_enable_component_w_entity(this->base_world(), this->base_id(), comp, false); + return *this; + } + + template<typename T, if_t< + !is_callable<T>::value && is_actual<T>::value> = 0 > + const Base& set(T&& value) const { + flecs::set<T>(this->base_world(), this->base_id(), std::forward<T&&>(value)); + return *this; + } + + template<typename T, if_t< + !is_callable<T>::value && is_actual<T>::value > = 0> + const Base& set(const T& value) const { + flecs::set<T>(this->base_world(), this->base_id(), value); + return *this; + } + + template<typename T, typename A = actual_type_t<T>, if_not_t< + is_callable<T>::value || is_actual<T>::value > = 0> + const Base& set(A&& value) const { + flecs::set<T>(this->base_world(), this->base_id(), std::forward<A&&>(value)); + return *this; + } + + template<typename T, typename A = actual_type_t<T>, if_not_t< + is_callable<T>::value || is_actual<T>::value > = 0> + const Base& set(const A& value) const { + flecs::set<T>(this->base_world(), this->base_id(), value); + return *this; + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses the relation as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam R The relation part of the pair. + * @tparam O The object part of the pair. + * @param value The value to set. + */ + template <typename R, typename O, typename P = pair<R, O>, + typename A = actual_type_t<P>, if_not_t< is_pair<R>::value> = 0> + const Base& set(const A& value) const { + flecs::set<P>(this->base_world(), this->base_id(), value); + return *this; + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses the relation as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam R The relation part of the pair. + * @param object The object part of the pair. + * @param value The value to set. + */ + template <typename R> + const Base& set(entity_t object, const R& value) const { + auto relation = _::cpp_type<R>::id(this->base_world()); + flecs::set(this->base_world(), this->base_id(), value, + ecs_pair(relation, object)); + return *this; + } + + /** Set a pair for an entity. + * This operation sets the pair value, and uses the relation as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Object The object part of the pair. + * @param relation The relation part of the pair. + * @param value The value to set. + */ + template <typename O> + const Base& set_w_object(entity_t relation, const O& value) const { + auto object = _::cpp_type<O>::id(this->base_world()); + flecs::set(this->base_world(), this->base_id(), value, + ecs_pair(relation, object)); + return *this; + } + + template <typename R, typename O> + const Base& set_w_object(const O& value) const { + flecs::set<pair_object<R, O>>(this->base_world(), this->base_id(), value); + return *this; + } + + /** Set 1..N components. + * This operation accepts a callback with as arguments the components to + * set. If the entity does not have all of the provided components, they + * will be added. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. When this operation is called + * while deferred, its performance is equivalent to that of calling get_mut + * for each component separately. + * + * The operation will invoke modified for each component after the callback + * has been invoked. + * + * @param func The callback to invoke. + */ + template <typename Func, if_t< is_callable<Func>::value > = 0> + const Base& set(const Func& func) const; + + /** Emplace component. + * Emplace constructs a component in the storage, which prevents calling the + * destructor on the object passed into the function. + * + * Emplace attempts the following signatures to construct the component: + * T{Args...} + * T{flecs::entity, Args...} + * + * If the second signature matches, emplace will pass in the current entity + * as argument to the constructor, which is useful if the component needs + * to be aware of the entity to which it has been added. + * + * Emplace may only be called for components that have not yet been added + * to the entity. + * + * @tparam T the component to emplace + * @param args The arguments to pass to the constructor of T + */ + template <typename T, typename ... Args> + const Base& emplace(Args&&... args) const { + flecs::emplace<T>(this->base_world(), this->base_id(), + std::forward<Args>(args)...); + return *this; + } + + /** Entities created in function will have the current entity. + * + * @param func The function to call. + */ + template <typename Func> + const Base& with(const Func& func) const { + ecs_id_t prev = ecs_set_with(this->base_world(), this->base_id()); + func(); + ecs_set_with(this->base_world(), prev); + return *this; + } + + /** Entities created in function will have (Relation, this) + * This operation is thread safe. + * + * @tparam Relation The relation to use. + * @param func The function to call. + */ + template <typename Relation, typename Func> + const Base& with(const Func& func) const { + with(_::cpp_type<Relation>::id(this->base_world()), func); + return *this; + } + + /** Entities created in function will have (relation, this) + * + * @param relation The relation to use. + * @param func The function to call. + */ + template <typename Func> + const Base& with(id_t relation, const Func& func) const { + ecs_id_t prev = ecs_set_with(this->base_world(), + ecs_pair(relation, this->base_id())); + func(); + ecs_set_with(this->base_world(), prev); + return *this; + } + + /** The function will be ran with the scope set to the current entity. */ + template <typename Func> + const Base& scope(const Func& func) const { + ecs_entity_t prev = ecs_set_scope(this->base_world(), this->base_id()); + func(); + ecs_set_scope(this->base_world(), prev); + return *this; + } + + /** Associate entity with type. + * This operation enables using a type to refer to an entity, as it + * associates the entity id with the provided type. + * + * If the entity does not have a name, a name will be derived from the type. + * If the entity already is a component, the provided type must match in + * size with the component size of the entity. After this operation the + * entity will be a component (it will have the EcsComponent component) if + * the type has a non-zero size. + * + * @tparam T the type to associate with the entity. + */ + template <typename T> + const Base& component() const; + + /* Set the entity name. + */ + const Base& set_name(const char *name) const { + ecs_set_name(this->base_world(), this->base_id(), name); + return *this; + } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Quick and safe access to a component pointer +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +class ref { +public: + ref() + : m_world( nullptr ) + , m_entity( 0 ) + , m_ref() { } + + ref(world_t *world, entity_t entity) + : m_world( world ) + , m_entity( entity ) + , m_ref() + { + auto comp_id = _::cpp_type<T>::id(world); + + ecs_assert(_::cpp_type<T>::size() != 0, + ECS_INVALID_PARAMETER, NULL); + + ecs_get_ref_w_id( + m_world, &m_ref, m_entity, comp_id); + } + + const T* operator->() { + const T* result = static_cast<const T*>(ecs_get_ref_w_id( + m_world, &m_ref, m_entity, _::cpp_type<T>::id(m_world))); + + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); + + return result; + } + + const T* get() { + if (m_entity) { + ecs_get_ref_w_id( + m_world, &m_ref, m_entity, _::cpp_type<T>::id(m_world)); + } + + return static_cast<T*>(m_ref.ptr); + } + + flecs::entity entity() const; + +private: + world_t *m_world; + entity_t m_entity; + flecs::ref_t m_ref; +}; + +/** Entity class + * This class provides access to entities. */ +class entity : + public entity_view, + public entity_builder<entity>, + public entity_deprecated<entity>, + public entity_builder_deprecated<entity> +{ +public: + /** Default constructor. + */ + entity() + : flecs::entity_view() { } + + /** Create entity. + * + * @param world The world in which to create the entity. + */ + explicit entity(world_t *world) + : flecs::entity_view() + { + m_world = world; + m_id = ecs_new_w_type(world, 0); + } + + /** 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. + * @param is_component If true, the entity will be created from the pool of component ids (default = false). + */ + explicit entity(world_t *world, const char *name) + : flecs::entity_view() + { + m_world = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = "::"; + m_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(world_t *world, entity_t id) + : flecs::entity_view() + { + m_world = world; + m_id = id; + } + + /** Conversion from flecs::entity_t to flecs::entity. */ + explicit entity(entity_t id) + : flecs::entity_view( nullptr, id ) { } + + /** Get entity id. + * @return The integer entity id. + */ + entity_t id() const { + return m_id; + } + + /** Get mutable component value. + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. + * + * @tparam T The component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + template <typename T> + T* get_mut(bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<T*>( + ecs_get_mut_w_entity(m_world, m_id, comp_id, is_added)); + } + + /** 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 component The component to get. + * @param is_added If provided, this parameter will be set to true if the component was added. + * @return Pointer to the component value. + */ + void* get_mut(entity_t comp, bool *is_added = nullptr) const { + return ecs_get_mut_w_entity(m_world, m_id, comp, is_added); + } + + /** Get mutable pointer for a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Relation the relation type. + * @tparam Object the object type. + */ + template <typename Relation, typename Object> + Relation* get_mut(bool *is_added = nullptr) const { + return this->get_mut<Relation>( + _::cpp_type<Object>::id(m_world), is_added); + } + + /** Get mutable pointer for a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Relation the relation type. + * @param object the object. + */ + template <typename Relation> + Relation* get_mut(entity_t object, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + ecs_assert(_::cpp_type<Relation>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<Relation*>( + ecs_get_mut_w_entity(m_world, m_id, + ecs_pair(comp_id, object), is_added)); + } + + /** Get mutable pointer for a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * relation or object are a component, the operation will fail. + * + * @param relation the relation. + * @param object the object. + */ + void* get_mut(entity_t relation, entity_t object, bool *is_added = nullptr) const { + return ecs_get_mut_w_entity(m_world, m_id, + ecs_pair(relation, object), is_added); + } + + /** Get mutable pointer for the object from a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Object the object type. + * @param relation the relation. + */ + template <typename Object> + Object* get_mut_w_object(entity_t relation, bool *is_added = nullptr) const { + auto comp_id = _::cpp_type<Object>::id(m_world); + ecs_assert(_::cpp_type<Object>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return static_cast<Object*>( + ecs_get_mut_w_entity(m_world, m_id, + ecs_pair(relation, comp_id), is_added)); + } + + /** Signal that component was modified. + * + * @tparam T component that was modified. + */ + template <typename T> + void modified() const { + auto comp_id = _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + this->modified(comp_id); + } + + /** Signal that the relation part of a pair was modified. + * + * @tparam Relation the relation type. + * @tparam Object the object type. + */ + template <typename Relation, typename Object> + void modified() const { + this->modified<Relation>(_::cpp_type<Object>::id(m_world)); + } + + /** Signal that the relation part of a pair was modified. + * + * @tparam Relation the relation type. + * @param object the object. + */ + template <typename Relation> + void modified(entity_t object) const { + auto comp_id = _::cpp_type<Relation>::id(m_world); + ecs_assert(_::cpp_type<Relation>::size() != 0, ECS_INVALID_PARAMETER, NULL); + this->modified(comp_id, object); + } + + /** Signal that a pair has modified (untyped). + * If neither the relation or object part of the pair are a component, the + * operation will fail. + * + * @param relation the relation. + * @param object the object. + */ + void modified(entity_t relation, entity_t object) const { + this->modified(ecs_pair(relation, object)); + } + + /** Signal that component was modified. + * + * @param component component that was modified. + */ + void modified(entity_t comp) const { + ecs_modified_w_entity(m_world, m_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 <typename T> + ref<T> get_ref() const { + // Ensure component is registered + _::cpp_type<T>::id(m_world); + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + return ref<T>(m_world, m_id); + } + + /** Clear an entity. + * This operation removes all components from an entity without recycling + * the entity id. + */ + void clear() const { + ecs_clear(m_world, m_id); + } + + /** Delete an entity. + * Entities have to be deleted explicitly, and are not deleted when the + * flecs::entity object goes out of scope. + */ + void destruct() const { + ecs_delete(m_world, m_id); + } + + /** Used by builder class. Do not invoke (deprecated). */ + template <typename Func> + void invoke(Func&& action) const { + action(m_world, m_id); + } + + /** Entity id 0. + * This function is useful when the API must provide an entity object that + * belongs to a world, but the entity id is 0. + * + * @param world The world. + */ + static + flecs::entity null(const flecs::world& world) { + return flecs::entity(world.get_world().c_ptr(), + static_cast<entity_t>(0)); + } + + static + flecs::entity null() { + return flecs::entity(static_cast<entity_t>(0)); + } +}; + +/** Prefab class */ +class prefab final : public entity { +public: + explicit prefab(world_t *world, const char *name = nullptr) + : entity(world, name) + { + this->add(flecs::Prefab); + } +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/filter.hpp b/fggl/ecs2/flecs/include/flecs/cpp/filter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..742e1a8ef5a845158e7ea90235d3ca41fe6b306f --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/filter.hpp @@ -0,0 +1,151 @@ +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Ad hoc queries (filters) +//////////////////////////////////////////////////////////////////////////////// + +class filter_base { +public: + filter_base() + : m_world(nullptr) + , m_filter({}) { } + + filter_base(world_t *world, ecs_filter_t *filter = NULL) + : m_world(world) { + ecs_filter_move(&m_filter, filter); + } + + /** Get pointer to C filter object. + */ + const filter_t* c_ptr() const { + if (m_filter.term_count) { + return &m_filter; + } else { + return NULL; + } + } + + filter_base(const filter_base& obj) { + this->m_world = obj.m_world; + ecs_filter_copy(&m_filter, &obj.m_filter); + } + + filter_base& operator=(const filter_base& obj) { + this->m_world = obj.m_world; + ecs_filter_copy(&m_filter, &obj.m_filter); + return *this; + } + + filter_base(filter_base&& obj) { + this->m_world = obj.m_world; + ecs_filter_move(&m_filter, &obj.m_filter); + } + + filter_base& operator=(filter_base&& obj) { + this->m_world = obj.m_world; + ecs_filter_move(&m_filter, &obj.m_filter); + return *this; + } + + + /** Free the filter. + */ + ~filter_base() { + ecs_filter_fini(&m_filter); + } + + template <typename Func> + void iter(Func&& func) const { + ecs_iter_t it = ecs_filter_iter(m_world, &m_filter); + while (ecs_filter_next(&it)) { + _::iter_invoker<Func>(func).invoke(&it); + } + } + + template <typename Func> + void each_term(const Func& func) { + for (int i = 0; i < m_filter.term_count; i ++) { + flecs::term t(m_world, m_filter.terms[i]); + func(t); + } + } + + flecs::term term(int32_t index) { + return flecs::term(m_world, m_filter.terms[index]); + } + + int32_t term_count() { + return m_filter.term_count; + } + + flecs::string str() { + char *result = ecs_filter_str(m_world, &m_filter); + return flecs::string(result); + } + +protected: + world_t *m_world; + filter_t m_filter; +}; + + +template<typename ... Components> +class filter : public filter_base { + using Terms = typename _::term_ptrs<Components...>::array; + +public: + filter() { } + + filter(world_t *world, filter_t *f) + : filter_base(world, f) { } + + explicit filter(const world& world, const char *expr = nullptr) + : filter_base(world.c_ptr()) + { + auto qb = world.filter_builder<Components ...>() + .expr(expr); + + if (!expr) { + qb.substitute_default(); + } + + flecs::filter_t f = qb; + ecs_filter_move(&m_filter, &f); + } + + filter(const filter& obj) : filter_base(obj) { } + + filter& operator=(const filter& obj) { + *this = obj; + return *this; + } + + filter(filter&& obj) : filter_base(std::move(obj)) { } + + filter& operator=(filter&& obj) { + filter_base(std::move(obj)); + return *this; + } + + template <typename Func> + void each(Func&& func) const { + iterate<_::each_invoker>(std::forward<Func>(func), ecs_filter_next); + } + + template <typename Func> + void iter(Func&& func) const { + iterate<_::iter_invoker>(std::forward<Func>(func), ecs_filter_next); + } + +private: + template < template<typename Func, typename ... Comps> class Invoker, typename Func, typename NextFunc, typename ... Args> + void iterate(Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = ecs_filter_iter(m_world, &m_filter); + while (next(&it, std::forward<Args>(args)...)) { + Invoker<Func, Components...>(std::move(func)).invoke(&it); + } + } +}; + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/filter_iterator.hpp b/fggl/ecs2/flecs/include/flecs/cpp/filter_iterator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2ad54f839cc275aa40f3264907ca7d8265c9368b --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/filter_iterator.hpp @@ -0,0 +1,173 @@ + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for iterating over tables that match a filter +//////////////////////////////////////////////////////////////////////////////// + +class filter_iterator +{ +public: + filter_iterator(ecs_iter_next_action_t action) + : m_world(nullptr) + , m_has_next(false) + , m_iter{ } + , m_action(action) { } + + filter_iterator(const world& world, ecs_iter_next_action_t action) + : m_world( world.c_ptr() ) + , m_iter( ecs_filter_iter(m_world, NULL) ) + , m_action(action) + { + m_has_next = m_action(&m_iter); + } + + filter_iterator(const world& world, const snapshot& snapshot, ecs_iter_next_action_t action) + : m_world( world.c_ptr() ) + , m_iter( ecs_snapshot_iter(snapshot.c_ptr(), NULL) ) + , m_action(action) + { + m_has_next = m_action(&m_iter); + } + + bool operator!=(filter_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + filter_iterator& operator++() { + m_has_next = m_action(&m_iter); + return *this; + } + +private: + world_t *m_world; + bool m_has_next; + ecs_iter_t m_iter; + ecs_iter_next_action_t m_action; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Tree iterator +//////////////////////////////////////////////////////////////////////////////// + +class tree_iterator +{ +public: + tree_iterator() + : m_has_next(false) + , m_iter{ } { } + + tree_iterator(flecs::world_t *world, const flecs::entity_t entity) + : m_iter( ecs_scope_iter(world, entity )) + { + m_has_next = ecs_scope_next(&m_iter); + } + + bool operator!=(tree_iterator const& other) const { + return m_has_next != other.m_has_next; + } + + flecs::iter const operator*() const { + return flecs::iter(&m_iter); + } + + tree_iterator& operator++() { + m_has_next = ecs_scope_next(&m_iter); + return *this; + } + +private: + bool m_has_next; + ecs_iter_t m_iter; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a world-based filter iterator +//////////////////////////////////////////////////////////////////////////////// + +class world_filter { +public: + world_filter(const world& world) + : m_world( world ) { } + + inline filter_iterator begin() const { + return filter_iterator(m_world, ecs_filter_next); + } + + inline filter_iterator end() const { + return filter_iterator(ecs_filter_next); + } + +private: + const world& m_world; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a snapshot-based filter iterator +//////////////////////////////////////////////////////////////////////////////// + +class snapshot_filter { +public: + snapshot_filter(const world& world, const snapshot& snapshot) + : m_world( world ) + , m_snapshot( snapshot ) { } + + inline filter_iterator begin() const { + return filter_iterator(m_world, m_snapshot, ecs_snapshot_next); + } + + inline filter_iterator end() const { + return filter_iterator(ecs_snapshot_next); + } + +private: + const world& m_world; + const snapshot& m_snapshot; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility for creating a child table iterator +//////////////////////////////////////////////////////////////////////////////// + +class child_iterator { +public: + child_iterator(const flecs::entity_view& entity) + : m_world( entity.world().c_ptr() ) + , m_parent( entity.id() ) { } + + inline tree_iterator begin() const { + return tree_iterator(m_world, m_parent); + } + + inline tree_iterator end() const { + return tree_iterator(); + } + +private: + flecs::world_t *m_world; + flecs::entity_t m_parent; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Filter fwd declared functions +//////////////////////////////////////////////////////////////////////////////// + +inline filter_iterator snapshot::begin() { + return filter_iterator(m_world, *this, ecs_snapshot_next); +} + +inline filter_iterator snapshot::end() { + return filter_iterator(ecs_snapshot_next); +} + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/function_traits.hpp b/fggl/ecs2/flecs/include/flecs/cpp/function_traits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ea4bd614f8c37281c49c2c7e782fa55578513cf8 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/function_traits.hpp @@ -0,0 +1,129 @@ + +// Neat utility to inspect arguments & returntype of a function type +// Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work + +namespace flecs { +namespace _ { + +template <typename ... Args> +struct arg_list { }; + +// Base type that contains the traits +template <typename ReturnType, typename... Args> +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<Args ...>; +}; + +// Primary template for function_traits_impl +template <typename T> +struct function_traits_impl { + static constexpr bool is_callable = false; +}; + +// Template specializations for the different kinds of function types (whew) +template <typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(Args...)> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(*)(Args...)> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...)> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&> + : function_traits_defs<ReturnType, Args...> {}; + +template <typename ClassType, typename ReturnType, typename... Args> +struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&&> + : function_traits_defs<ReturnType, Args...> {}; + +// 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 <typename T, typename V = void> +struct function_traits_no_cv + : function_traits_impl<T> {}; + +// Specialized template for function types +template <typename T> +struct function_traits_no_cv<T, decltype((void)&T::operator())> + : function_traits_impl<decltype(&T::operator())> {}; + +// Front facing template that decays T before ripping it apart. +template <typename T> +struct function_traits + : function_traits_no_cv< decay_t<T> > {}; + +} // _ + + +template <typename T> +struct is_callable { + static constexpr bool value = _::function_traits<T>::is_callable; +}; + +template <typename T> +struct arity { + static constexpr int value = _::function_traits<T>::arity; +}; + +template <typename T> +using return_type_t = typename _::function_traits<T>::return_type; + +template <typename T> +using arg_list_t = typename _::function_traits<T>::args; + + +template<typename Func, typename ... Args> +struct first_arg_impl; + +template<typename Func, typename T, typename ... Args> +struct first_arg_impl<Func, _::arg_list<T, Args ...> > { + using type = T; +}; + +template<typename Func> +struct first_arg { + using type = typename first_arg_impl<Func, arg_list_t<Func>>::type; +}; + +template <typename Func> +using first_arg_t = typename first_arg<Func>::type; + +} // flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/id.hpp b/fggl/ecs2/flecs/include/flecs/cpp/id.hpp new file mode 100644 index 0000000000000000000000000000000000000000..bf962a4224ccb8f88cab99a28493c1e418b062f3 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/id.hpp @@ -0,0 +1,137 @@ + +namespace flecs { + +/** Class that stores a flecs id. + * A flecs id is an identifier that can store an entity id, an relation-object + * pair, or role annotated id (such as SWITCH | Movement). + */ +class id : public world_base<id> { +public: + explicit id(flecs::id_t value = 0) + : m_world(nullptr) + , m_id(value) { } + + explicit id(flecs::world_t *world, flecs::id_t value = 0) + : m_world(world) + , m_id(value) { } + + explicit id(flecs::world_t *world, flecs::id_t relation, flecs::id_t object) + : m_world(world) + , m_id(ecs_pair(relation, object)) { } + + explicit id(flecs::id_t relation, flecs::id_t object) + : m_world(nullptr) + , m_id(ecs_pair(relation, object)) { } + + explicit id(const flecs::id& relation, const flecs::id& object) + : m_world(relation.world()) + , m_id(ecs_pair(relation.m_id, object.m_id)) { } + + /** Test if id is pair (has relation, object) */ + bool is_pair() const { + return (m_id & ECS_ROLE_MASK) == flecs::Pair; + } + + /* Test if id is a wildcard */ + bool is_wildcard() const { + return ecs_id_is_wildcard(m_id); + } + + /* Test if id has the Switch role */ + bool is_switch() const { + return (m_id & ECS_ROLE_MASK) == flecs::Switch; + } + + /* Test if id has the Case role */ + bool is_case() const { + return (m_id & ECS_ROLE_MASK) == flecs::Case; + } + + /* Return id as entity (only allowed when id is valid entity) */ + flecs::entity entity() const; + + /* Return id with role added */ + flecs::entity add_role(flecs::id_t role) const; + + /* Return id with role removed */ + flecs::entity remove_role(flecs::id_t role) const; + + /* Return id without role */ + flecs::entity remove_role() const; + + /* Return id without role */ + flecs::entity remove_generation() const; + + /* Test if id has specified role */ + bool has_role(flecs::id_t role) const { + return ((m_id & ECS_ROLE_MASK) == role); + } + + /* Test if id has any role */ + bool has_role() const { + return (m_id & ECS_ROLE_MASK) != 0; + } + + flecs::entity role() const; + + /* Test if id has specified relation */ + bool has_relation(flecs::id_t relation) const { + if (!is_pair()) { + return false; + } + return ECS_PAIR_RELATION(m_id) == relation; + } + + /** Get relation from 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 relation() const; + + /** Get object from 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 object() const; + + /* Convert id to string */ + flecs::string str() const { + size_t size = ecs_id_str(m_world, m_id, NULL, 0); + char *result = static_cast<char*>(ecs_os_malloc( + static_cast<ecs_size_t>(size) + 1)); + ecs_id_str(m_world, m_id, result, size + 1); + return flecs::string(result); + } + + /** Convert role of id to string. */ + flecs::string role_str() const { + return flecs::string_view( ecs_role_str(m_id & ECS_ROLE_MASK)); + } + + ECS_DEPRECATED("use object()") + flecs::entity lo() const; + + ECS_DEPRECATED("use relation()") + flecs::entity hi() const; + + ECS_DEPRECATED("use flecs::id(relation, object)") + static + flecs::entity comb(entity_view lo, entity_view hi); + + flecs::id_t raw_id() const { + return m_id; + } + + operator flecs::id_t() const { + return m_id; + } + + /* World is optional, but guarantees that entity identifiers extracted from + * the id are valid */ + flecs::world_t *m_world; + flecs::id_t m_id; +}; + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9b204064ddf0b94e76c88d8234af14445d2d4203 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl.hpp @@ -0,0 +1,7 @@ + +#include "impl/lifecycle_traits.hpp" +#include "impl/id.hpp" +#include "impl/entity.hpp" +#include "impl/iter.hpp" +#include "impl/world.hpp" +#include "impl/builder.hpp" diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl/builder.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl/builder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..edb65579856ab6f4520231429c9c5ed4138e9654 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl/builder.hpp @@ -0,0 +1,97 @@ + +namespace flecs +{ + +template<typename Base> +inline Base& term_builder_i<Base>::id(const flecs::type& type) { + ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); + m_term->pred.entity = type.id(); + return *this; +} + +template <typename ... Components> +inline filter_builder_base<Components...>::operator filter<Components ...>() const { + ecs_filter_t filter = *this; + return flecs::filter<Components...>(m_world, &filter); +} + +template <typename ... Components> +inline filter_builder<Components ...>::operator filter<>() const { + ecs_filter_t filter = *this; + return flecs::filter<>(this->m_world, &filter); +} + +template <typename ... Components> +inline filter<Components ...> filter_builder_base<Components...>::build() const { + ecs_filter_t filter = *this; + return flecs::filter<Components...>(m_world, &filter); +} + +template <typename ... Components> +inline query_builder_base<Components...>::operator query<Components ...>() const { + ecs_query_t *query = *this; + return flecs::query<Components...>(m_world, query); +} + +template <typename ... Components> +inline query_builder<Components ...>::operator query<>() const { + ecs_query_t *query = *this; + return flecs::query<>(this->m_world, query); +} + +template <typename ... Components> +inline query<Components ...> query_builder_base<Components...>::build() const { + ecs_query_t *query = *this; + return flecs::query<Components...>(m_world, query); +} + +template <typename Base, typename ... Components> +inline Base& query_builder_i<Base, Components ...>::parent(const query_base& parent) { + m_desc->parent = parent.c_ptr(); + return *static_cast<Base*>(this); +} + +template <typename ... Components> +template <typename Func> +inline system<Components ...> system_builder<Components...>::action(Func&& func) const { + flecs::entity_t system = build<action_invoker_t<Func>>(std::forward<Func>(func), false); + return flecs::system<Components...>(m_world, system); +} + +template <typename ... Components> +template <typename Func> +inline system<Components ...> system_builder<Components...>::iter(Func&& func) const { + using Invoker = typename _::iter_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t system = build<Invoker>(std::forward<Func>(func), false); + return flecs::system<Components...>(m_world, system); +} + +template <typename ... Components> +template <typename Func> +inline system<Components ...> system_builder<Components...>::each(Func&& func) const { + using Invoker = typename _::each_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t system = build<Invoker>(std::forward<Func>(func), true); + return flecs::system<Components...>(m_world, system); +} + +template <typename ... Components> +template <typename Func> +inline observer<Components ...> observer_builder<Components...>::iter(Func&& func) const { + using Invoker = typename _::iter_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t observer = build<Invoker>(std::forward<Func>(func), false); + return flecs::observer<Components...>(m_world, observer); +} + +template <typename ... Components> +template <typename Func> +inline observer<Components ...> observer_builder<Components...>::each(Func&& func) const { + using Invoker = typename _::each_invoker< + typename std::decay<Func>::type, Components...>; + flecs::entity_t observer = build<Invoker>(std::forward<Func>(func), true); + return flecs::observer<Components...>(m_world, observer); +} + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl/entity.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl/entity.hpp new file mode 100644 index 0000000000000000000000000000000000000000..04f0f03eeb83b2cdb5dd06859d65ea3a463104de --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl/entity.hpp @@ -0,0 +1,221 @@ + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Entity range, allows for operating on a range of consecutive entities +//////////////////////////////////////////////////////////////////////////////// + +class ECS_DEPRECATED("do not use") entity_range final { +public: + entity_range(const world& world, int32_t count) + : m_world(world.c_ptr()) + , m_ids( ecs_bulk_new_w_type(m_world, nullptr, count)) { } + + entity_range(const world& world, int32_t count, flecs::type type) + : m_world(world.c_ptr()) + , m_ids( ecs_bulk_new_w_type(m_world, type.c_ptr(), count)) { } + +private: + world_t *m_world; + const entity_t *m_ids; +}; + +template <typename T> +flecs::entity ref<T>::entity() const { + return flecs::entity(m_world, m_entity); +} + +template <typename Base> +inline const Base& entity_builder<Base>::add(const type& type) const { + ecs_add_type(this->base_world(), this->base_id(), type.c_ptr()); + return *this; +} + +template <typename Base> +inline const Base& entity_builder<Base>::remove(const type& type) const { + ecs_remove_type(this->base_world(), this->base_id(), type.c_ptr()); + return *this; +} + +template <typename Base> +inline const Base& entity_builder<Base>::add_owned(const type& type) const { + return add_owned(type.id()); +} + +template <typename Base> +inline const Base& entity_builder<Base>::add_switch(const type& sw) const { + return add_switch(sw.id()); +} + +template <typename Base> +inline const Base& entity_builder<Base>::remove_switch(const type& sw) const { + return remove_switch(sw.id()); +} + +template <typename Base> +template <typename Func, if_t< is_callable<Func>::value > > +inline const Base& entity_builder<Base>::set(const Func& func) const { + _::entity_with_invoker<Func>::invoke_get_mut( + this->base_world(), this->base_id(), func); + return *this; +} + +template <typename Base> +template <typename T> +inline const Base& entity_builder<Base>::component() const { + component_for_id<T>(this->base_world(), this->base_id()); + return *this; +} + +inline bool entity_view::has_switch(const flecs::type& type) const { + return ecs_has_entity(m_world, m_id, flecs::Switch | type.id()); +} + +inline flecs::entity entity_view::get_case(const flecs::type& sw) const { + return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw.id())); +} + +inline flecs::entity entity_view::get_case(flecs::id_t sw) const { + return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw)); +} + +template <typename T> +inline flecs::entity entity_view::get_case() const { + return get_case(_::cpp_type<T>::id(m_world)); +} + +inline flecs::entity entity_view::get_object( + flecs::entity_t relation, + int32_t index) const +{ + return flecs::entity(m_world, + ecs_get_object(m_world, m_id, relation, index)); +} + +inline flecs::entity entity_view::mut(const flecs::world& stage) const { + ecs_assert(!stage.is_readonly(), ECS_INVALID_PARAMETER, + "cannot use readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(stage.c_ptr()); +} + +/** Same as mut(world), but for iterator. + * This operation allows for the construction of a mutable entity handle + * from an iterator. + * + * @param stage An created for the current stage. + * @return An entity handle that allows for mutations in the current stage. + */ +inline flecs::entity entity_view::mut(const flecs::iter& it) const { + ecs_assert(!it.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use iterator created for readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(it.world().c_ptr()); +} + +/** 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 stage An created for the current stage. + * @return An entity handle that allows for mutations in the current stage. + */ +inline flecs::entity entity_view::mut(const flecs::entity_view& e) const { + ecs_assert(!e.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use entity created for readonly world/stage to create mutable handle"); + return flecs::entity(m_id).set_stage(e.m_world); +} + +inline flecs::entity entity_view::set_stage(world_t *stage) { + m_world = stage; + return flecs::entity(m_world, m_id); +} + +inline flecs::type entity_view::type() const { + return flecs::type(m_world, ecs_get_type(m_world, m_id)); +} + +inline flecs::type entity_view::to_type() const { + ecs_type_t type = ecs_type_from_id(m_world, m_id); + return flecs::type(m_world, type); +} + +inline child_iterator entity_view::children() const { + ecs_assert(m_id != 0, ECS_INVALID_PARAMETER, NULL); + return flecs::child_iterator(*this); +} + +template <typename Func> +inline void entity_view::each(const Func& func) const { + const ecs_vector_t *type = ecs_get_type(m_world, m_id); + if (!type) { + return; + } + + const ecs_id_t *ids = static_cast<ecs_id_t*>( + _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); + int32_t count = ecs_vector_count(type); + + for (int i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + flecs::id ent(m_world, id); + func(ent); + + // Case is not stored in type, so handle separately + if ((id & ECS_ROLE_MASK) == flecs::Switch) { + ent = flecs::id( + m_world, flecs::Case | ecs_get_case( + m_world, m_id, ent.object().id())); + func(ent); + } + } +} + +template <typename Func> +inline void entity_view::match(id_t pattern, const Func& func) const { + const ecs_vector_t *type = ecs_get_type(m_world, m_id); + if (!type) { + return; + } + + id_t *ids = static_cast<ecs_id_t*>( + _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); + int32_t cur = 0; + + while (-1 != (cur = ecs_type_index_of(type, cur, pattern))) { + flecs::id ent(m_world, ids[cur]); + func(ent); + cur ++; + } +} + +template <typename Func> +inline void entity_view::each(const flecs::entity_view& rel, const Func& func) const { + return this->match(ecs_pair(rel, flecs::Wildcard), [&](flecs::id id) { + flecs::entity obj = id.object(); + func(obj); + }); +} + +template <typename Func, if_t< is_callable<Func>::value > > +inline bool entity_view::get(const Func& func) const { + return _::entity_with_invoker<Func>::invoke_get(m_world, m_id, func); +} + +template <typename T> +inline flecs::entity entity_view::get_parent() { + return flecs::entity(m_world, ecs_get_parent_w_entity(m_world, m_id, + _::cpp_type<T>::id(m_world))); +} + +inline flecs::entity entity_view::get_parent(flecs::entity_view e) { + return flecs::entity(m_world, + ecs_get_parent_w_entity(m_world, m_id, e.id())); +} + +inline flecs::entity entity_view::lookup(const char *path) const { + auto id = ecs_lookup_path_w_sep(m_world, m_id, path, "::", "::", false); + return flecs::entity(m_world, id); +} + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl/id.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl/id.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4c1b0944ca098b6b7b0db0071c437d82202f1c95 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl/id.hpp @@ -0,0 +1,66 @@ + +namespace flecs +{ + +inline flecs::entity id::entity() const { + ecs_assert(!is_pair(), ECS_INVALID_OPERATION, NULL); + ecs_assert(!role(), ECS_INVALID_OPERATION, NULL); + return flecs::entity(m_world, m_id); +} + +inline flecs::entity id::role() const { + return flecs::entity(m_world, m_id & ECS_ROLE_MASK); +} + +inline flecs::entity id::relation() const { + ecs_assert(is_pair(), ECS_INVALID_OPERATION, NULL); + + flecs::entity_t e = ECS_PAIR_RELATION(m_id); + if (m_world) { + return flecs::entity(m_world, ecs_get_alive(m_world, e)); + } else { + return flecs::entity(e); + } +} + +inline flecs::entity id::object() const { + flecs::entity_t e = ECS_PAIR_OBJECT(m_id); + if (m_world) { + return flecs::entity(m_world, ecs_get_alive(m_world, e)); + } else { + return flecs::entity(m_world, e); + } +} + +inline flecs::entity id::add_role(flecs::id_t role) const { + return flecs::entity(m_world, m_id | role); +} + +inline flecs::entity id::remove_role(flecs::id_t role) const { + (void)role; + ecs_assert((m_id & ECS_ROLE_MASK) == role, ECS_INVALID_PARAMETER, NULL); + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_role() const { + return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_generation() const { + return flecs::entity(m_world, static_cast<uint32_t>(m_id)); +} + +inline entity id::lo() const { + return flecs::entity(m_world, ecs_entity_t_lo(m_id)); +} + +inline entity id::hi() const { + return flecs::entity(m_world, ecs_entity_t_hi(m_id)); +} + +inline entity id::comb(entity_view lo, entity_view hi) { + return flecs::entity(lo.world(), + ecs_entity_t_comb(lo.id(), hi.id())); +} + +} diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl/iter.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl/iter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..05d1b3422a5c93d8a673d607066f2d4bba7e5bf4 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl/iter.hpp @@ -0,0 +1,69 @@ + +namespace flecs +{ + +inline flecs::entity iter::system() const { + return flecs::entity(m_iter->world, m_iter->system); +} + + inline flecs::entity iter::self() const { + return flecs::entity(m_iter->world, m_iter->self); +} + +inline flecs::world iter::world() const { + return flecs::world(m_iter->world); +} + +inline flecs::entity iter::entity(size_t row) const { + ecs_assert(row < static_cast<size_t>(m_iter->count), ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + if (!this->world().is_readonly()) { + return flecs::entity(m_iter->entities[row]) + .mut(this->world()); + } else { + return flecs::entity(this->world().c_ptr(), m_iter->entities[row]); + } +} + +/* Obtain column source (0 if self) */ +template <typename Base> +inline flecs::entity iter_deprecated<Base>::column_source(int32_t col) const { + return flecs::entity(iter()->world, ecs_term_source(iter(), col)); +} + +/* Obtain component/tag entity of column */ +template <typename Base> +inline flecs::entity iter_deprecated<Base>::column_entity(int32_t col) const { + return flecs::entity(iter()->world, ecs_term_id(iter(), col)); +} + +/* Obtain type of column */ +template <typename Base> +inline type iter_deprecated<Base>::column_type(int32_t col) const { + return flecs::type(iter()->world, ecs_column_type(iter(), col)); +} + +/* Obtain type of table being iterated over */ +template <typename Base> +inline type iter_deprecated<Base>::table_type() const { + return flecs::type(iter()->world, ecs_iter_type(iter())); +} + +template <typename T> +inline column<T>::column(iter &iter, int32_t index) { + *this = iter.term<T>(index); +} + +inline flecs::entity iter::term_source(int32_t index) const { + return flecs::entity(m_iter->world, ecs_term_source(m_iter, index)); +} + +inline flecs::entity iter::term_id(int32_t index) const { + return flecs::entity(m_iter->world, ecs_term_id(m_iter, index)); +} + +/* Obtain type of iter */ +inline flecs::type iter::type() const { + return flecs::type(m_iter->world, ecs_iter_type(m_iter)); +} + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl/lifecycle_traits.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl/lifecycle_traits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e2f8c8f3ebd5ff8df078246da8e566723d5be94e --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl/lifecycle_traits.hpp @@ -0,0 +1,24 @@ + + +namespace flecs +{ + +namespace _ +{ + +template <typename T> +inline void ctor_world_entity_impl( + ecs_world_t* world, ecs_entity_t, const ecs_entity_t* ids, void *ptr, + size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast<T*>(ptr); + flecs::world w(world); + for (int i = 0; i < count; i ++) { + flecs::entity e(world, ids[i]); + FLECS_PLACEMENT_NEW(&arr[i], T(w, e)); + } +} + +} // _ +} // flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/impl/world.hpp b/fggl/ecs2/flecs/include/flecs/cpp/impl/world.hpp new file mode 100644 index 0000000000000000000000000000000000000000..00cd18671c3a7b38a535c7da5f322e32b69be83e --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/impl/world.hpp @@ -0,0 +1,345 @@ + +namespace flecs +{ + +// emplace for T(flecs::entity, Args...) +template <typename T, typename ... Args, if_t< + std::is_constructible<actual_type_t<T>, flecs::entity, Args...>::value >> +inline void emplace(world_t *world, id_t entity, Args&&... args) { + flecs::entity self(world, entity); + emplace<T>(world, entity, self, std::forward<Args>(args)...); +} + +/** Get id from a type. */ +template <typename T> +inline flecs::id world::id() const { + return flecs::id(m_world, _::cpp_type<T>::id(m_world)); +} + +template <typename ... Args> +inline flecs::id world::id(Args&&... args) const { + return flecs::id(m_world, std::forward<Args>(args)...); +} + +template <typename R, typename O> +inline flecs::id world::pair() const { + return flecs::id( + m_world, + ecs_pair( + _::cpp_type<R>::id(m_world), + _::cpp_type<O>::id(m_world))); +} + +template <typename R> +inline flecs::id world::pair(entity_t o) const { + return flecs::id( + m_world, + ecs_pair( + _::cpp_type<R>::id(m_world), + o)); +} + +inline flecs::id world::pair(entity_t r, entity_t o) const { + return flecs::id( + m_world, + ecs_pair(r, o)); +} + +inline filter_iterator world::begin() const { + return filter_iterator(*this, ecs_filter_next); +} + +inline filter_iterator world::end() const { + return filter_iterator(ecs_filter_next); +} + +/** All entities created in function are created in scope. All operations + * called in function (such as lookup) are relative to scope. + */ +template <typename Func> +void scope(id_t parent, const Func& func); + +inline void world::init_builtin_components() { + pod_component<Component>("flecs::core::Component"); + pod_component<Type>("flecs::core::Type"); + pod_component<Identifier>("flecs::core::Identifier"); + pod_component<Trigger>("flecs::core::Trigger"); + pod_component<Observer>("flecs::core::Observer"); + pod_component<Query>("flecs::core::Query"); + + pod_component<TickSource>("flecs::system::TickSource"); + pod_component<RateFilter>("flecs::timer::RateFilter"); + pod_component<Timer>("flecs::timer::Timer"); +} + +template <typename T> +inline flecs::entity world::use(const char *alias) { + entity_t e = _::cpp_type<T>::id(m_world); + const char *name = alias; + if (!name) { + // If no name is defined, use the entity name without the scope + name = ecs_get_name(m_world, e); + } + ecs_use(m_world, e, name); + return flecs::entity(m_world, e); +} + +inline flecs::entity world::use(const char *name, const char *alias) { + entity_t e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", true); + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_use(m_world, e, alias); + return flecs::entity(m_world, e); +} + +inline void world::use(flecs::entity e, const char *alias) { + entity_t eid = e.id(); + const char *name = alias; + if (!name) { + // If no name is defined, use the entity name without the scope + ecs_get_name(m_world, eid); + } + ecs_use(m_world, eid, alias); +} + +inline flecs::entity world::set_scope(const flecs::entity& s) const { + return flecs::entity(ecs_set_scope(m_world, s.id())); +} + +inline flecs::entity world::get_scope() const { + return flecs::entity(ecs_get_scope(m_world)); +} + +inline entity world::lookup(const char *name) const { + auto e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", true); + return flecs::entity(*this, e); +} + +template <typename T> +T* world::get_mut() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.get_mut<T>(); +} + +template <typename T> +void world::modified() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.modified<T>(); +} + +template <typename T, typename Func> +void world::patch(const Func& func) const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + e.patch<T>(func); +} + +template <typename T> +const T* world::get() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.get<T>(); +} + +template <typename T> +bool world::has() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + return e.has<T>(); +} + +template <typename T> +void world::add() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + e.add<T>(); +} + +template <typename T> +void world::remove() const { + flecs::entity e(m_world, _::cpp_type<T>::id(m_world)); + e.remove<T>(); +} + +inline void world::set_pipeline(const flecs::pipeline& pip) const { + ecs_set_pipeline(m_world, pip.id()); +} + +template <typename T> +inline flecs::entity world::singleton() { + return flecs::entity(m_world, _::cpp_type<T>::id(m_world)); +} + +template <typename... Args> +inline flecs::entity world::entity(Args &&... args) const { + return flecs::entity(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::entity world::prefab(Args &&... args) const { + return flecs::prefab(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::type world::type(Args &&... args) const { + return flecs::type(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::pipeline world::pipeline(Args &&... args) const { + return flecs::pipeline(*this, std::forward<Args>(args)...); +} + +inline flecs::system<> world::system(flecs::entity e) const { + return flecs::system<>(m_world, e); +} + +template <typename... Comps, typename... Args> +inline flecs::system_builder<Comps...> world::system(Args &&... args) const { + return flecs::system_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::observer_builder<Comps...> world::observer(Args &&... args) const { + return flecs::observer_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::filter<Comps...> world::filter(Args &&... args) const { + return flecs::filter<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::filter_builder<Comps...> world::filter_builder(Args &&... args) const { + return flecs::filter_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::query<Comps...> world::query(Args &&... args) const { + return flecs::query<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Comps, typename... Args> +inline flecs::query_builder<Comps...> world::query_builder(Args &&... args) const { + return flecs::query_builder<Comps...>(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::term world::term(Args &&... args) const { + return flecs::term(*this, std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +inline flecs::term world::term(Args &&... args) const { + return flecs::term(*this, std::forward<Args>(args)...).id<T>(); +} + +template <typename R, typename O, typename... Args> +inline flecs::term world::term(Args &&... args) const { + return flecs::term(*this, std::forward<Args>(args)...).id<R, O>(); +} + +template <typename Module, typename... Args> +inline flecs::entity world::module(Args &&... args) const { + return flecs::module<Module>(*this, std::forward<Args>(args)...); +} + +template <typename Module> +inline flecs::entity world::import() { + return flecs::import<Module>(*this); +} + +template <typename T, typename... Args> +inline flecs::entity world::component(Args &&... args) const { + return flecs::component<T>(*this, std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +inline flecs::entity world::pod_component(Args &&... args) const { + return flecs::pod_component<T>(*this, std::forward<Args>(args)...); +} + +template <typename T, typename... Args> +inline flecs::entity world::relocatable_component(Args &&... args) const { + return flecs::relocatable_component<T>(*this, std::forward<Args>(args)...); +} + +template <typename... Args> +inline flecs::snapshot world::snapshot(Args &&... args) const { + return flecs::snapshot(*this, std::forward<Args>(args)...); +} + +template <typename T, typename Func> +inline void world::each(Func&& func) const { + ecs_term_t t = {}; + t.id = _::cpp_type<T>::id(); + ecs_iter_t it = ecs_term_iter(m_world, &t); + + while (ecs_term_next(&it)) { + _::each_invoker<Func, T>(func).invoke(&it); + } +} + +template <typename Func> +inline void world::each(flecs::id_t term_id, Func&& func) const { + ecs_term_t t = {}; + t.id = term_id; + ecs_iter_t it = ecs_term_iter(m_world, &t); + + while (ecs_term_next(&it)) { + _::each_invoker<Func>(func).invoke(&it); + } +} + +namespace _ { + +// Each with entity parameter +template<typename Func, typename ... Args> +struct filter_invoker_w_ent; + +template<typename Func, typename E, typename ... Args> +struct filter_invoker_w_ent<Func, arg_list<E, Args ...> > +{ + filter_invoker_w_ent(const flecs::world& world, Func&& func) { + flecs::filter<Args ...> f(world); + f.each(std::move(func)); + } +}; + +// Each without entity parameter +template<typename Func, typename ... Args> +struct filter_invoker_no_ent; + +template<typename Func, typename ... Args> +struct filter_invoker_no_ent<Func, arg_list<Args ...> > +{ + filter_invoker_no_ent(const flecs::world& world, Func&& func) { + flecs::filter<Args ...> f(world); + f.each(std::move(func)); + } +}; + +// Switch between function with & without entity parameter +template<typename Func, typename T = int> +class filter_invoker; + +template <typename Func> +class filter_invoker<Func, if_t<is_same<first_arg_t<Func>, flecs::entity>::value> > { +public: + filter_invoker(const flecs::world& world, Func&& func) { + filter_invoker_w_ent<Func, arg_list_t<Func>>(world, std::move(func)); + } +}; + +template <typename Func> +class filter_invoker<Func, if_not_t<is_same<first_arg_t<Func>, flecs::entity>::value> > { +public: + filter_invoker(const flecs::world& world, Func&& func) { + filter_invoker_no_ent<Func, arg_list_t<Func>>(world, std::move(func)); + } +}; + +} + +template <typename Func> +inline void world::each(Func&& func) const { + _::filter_invoker<Func> f_invoker(*this, std::move(func)); +} + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/invoker.hpp b/fggl/ecs2/flecs/include/flecs/cpp/invoker.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3c92b77272696487979c984679a79aaab0130a03 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/invoker.hpp @@ -0,0 +1,548 @@ + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system each +//////////////////////////////////////////////////////////////////////////////// + +namespace flecs +{ + +namespace _ +{ + +// Utility to convert template argument pack to array of term ptrs +struct term_ptr { + void *ptr; + bool is_ref; +}; + +template <typename ... Components> +class term_ptrs { +public: + 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<Components>> + *>(nullptr)...); + } + + array m_terms; + +private: + /* Populate terms array without checking for references */ + bool populate(const ecs_iter_t*, size_t) { return false; } + + template <typename T, typename... Targs> + bool populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { + m_terms[index].ptr = iter->ptrs[index]; + bool is_ref = iter->subjects && iter->subjects[index] != 0; + m_terms[index].is_ref = is_ref; + is_ref |= populate(iter, index + 1, comps ...); + return is_ref; + } +}; + +class invoker { }; + +// Template that figures out from the template parameters of a query/system +// how to pass the value to the each callback +template <typename T, typename = int> +struct each_column { }; + +// Base class +struct each_column_base { + each_column_base(const _::term_ptr& term, size_t row) + : m_term(term), m_row(row) { } + +protected: + const _::term_ptr& m_term; + size_t m_row; +}; + +// If type is not a pointer, return a reference to the type (default case) +template <typename T> +struct each_column<T, if_t< !is_pointer<T>::value && is_actual<T>::value > > + : public each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T& get_row() { + return static_cast<T*>(this->m_term.ptr)[this->m_row]; + } +}; + +// If argument type is not the same as actual component type, return by value. +// This requires that the actual type can be converted to the type. +// A typical scenario where this happens is when using flecs::pair types. +template <typename T> +struct each_column<T, if_t< !is_pointer<T>::value && !is_actual<T>::value> > + : public each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + return static_cast<actual_type_t<T>*>(this->m_term.ptr)[this->m_row]; + } +}; + +// If type is a pointer (indicating an optional value) return the type as is +template <typename T> +struct each_column<T, if_t< is_pointer<T>::value > > + : public each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + if (this->m_term.ptr) { + return &static_cast<actual_type_t<T>>(this->m_term.ptr)[this->m_row]; + } else { + // optional argument doesn't hava a value + return nullptr; + } + } +}; + +// If the query contains component references to other entities, check if the +// current argument is one. +template <typename T, typename = int> +struct each_ref_column : public each_column<T> { + each_ref_column(const _::term_ptr& term, size_t row) + : each_column<T>(term, row) { + + if (term.is_ref) { + // If this is a reference, set the row to 0 as a ref always is a + // single value, not an array. This prevents the application from + // having to do an if-check on whether the column is owned. + // + // This check only happens when the current table being iterated + // over caused the query to match a reference. The check is + // performed once per iterated table. + this->m_row = 0; + } + } +}; + +template <typename Func, typename ... Components> +class each_invoker : public invoker { +public: + // If the number of arguments in the function signature is one more than the + // number of components in the query, an extra entity arg is required. + static constexpr bool PassEntity = + sizeof...(Components) == (arity<Func>::value - 1); + + static_assert(arity<Func>::value > 0, + "each() must have at least one argument"); + + using Terms = typename term_ptrs<Components ...>::array; + + explicit each_invoker(Func&& func) noexcept + : m_func(std::move(func)) { } + + explicit each_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs<Components...> terms; + + if (terms.populate(iter)) { + invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + } else { + invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + } + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast<const each_invoker*>(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template <template<typename X, typename = int> class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { +#ifndef NDEBUG + ecs_table_t *table = iter->table; + if (table) { + ecs_table_lock(iter->world, table); + } +#endif + + flecs::iter it(iter); + for (auto row : it) { + func(it.entity(row), + (ColumnType< remove_reference_t<Components> >(comps, row) + .get_row())...); + } + +#ifndef NDEBUG + if (table) { + ecs_table_unlock(iter->world, table); + } +#endif + } + + // Number of function arguments is equal to number of components, no entity + template <template<typename X, typename = int> class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && !PassEntity> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + flecs::iter it(iter); + for (auto row : it) { + func( (ColumnType< remove_reference_t<Components> >(comps, row) + .get_row())...); + } + } + + template <template<typename X, typename = int> class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + invoke_callback<ColumnType>( + iter, func, index + 1, columns, comps..., columns[index]); + } + + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system iterate action +//////////////////////////////////////////////////////////////////////////////// + +template <typename Func, typename ... Components> +class iter_invoker : public invoker { + static constexpr bool IterOnly = arity<Func>::value == 1; + + using Terms = typename term_ptrs<Components ...>::array; + +public: + explicit iter_invoker(Func&& func) noexcept + : m_func(std::move(func)) { } + + explicit iter_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs<Components...> terms; + terms.populate(iter); + invoke_callback(iter, m_func, 0, terms.m_terms); + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast<const iter_invoker*>(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + +private: + template <typename... Args, if_t<!sizeof...(Args) && IterOnly> = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t, Terms&, Args...) + { + flecs::iter it(iter); + func(it); + } + + template <typename... Targs, if_t<!IterOnly && + (sizeof...(Targs) == sizeof...(Components))> = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, size_t, + Terms&, Targs... comps) + { + flecs::iter it(iter); + +#ifndef NDEBUG + ecs_table_t *table = iter->table; + if (table) { + ecs_table_lock(iter->world, table); + } +#endif + + func(it, ( static_cast< + remove_reference_t< + remove_pointer_t< + actual_type_t<Components> > >* > + (comps.ptr))...); + +#ifndef NDEBUG + if (table) { + ecs_table_unlock(iter->world, table); + } +#endif + } + + template <typename... Targs, if_t<!IterOnly && + (sizeof...(Targs) != sizeof...(Components)) > = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Targs... comps) + { + invoke_callback(iter, func, index + 1, columns, comps..., + columns[index]); + } + + Func m_func; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system action (deprecated) +//////////////////////////////////////////////////////////////////////////////// + +template <typename Func, typename ... Components> +class action_invoker : public invoker { + using Terms = typename term_ptrs<Components ...>::array; +public: + explicit action_invoker(Func&& func) noexcept : m_func(std::move(func)) { } + explicit action_invoker(const Func& func) noexcept : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs<Components...> terms; + terms.populate(iter); + invoke_callback(iter, m_func, 0, terms.m_terms); + } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast<const action_invoker*>(iter->binding_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + +private: + template <typename... Targs, + if_t< sizeof...(Targs) == sizeof...(Components) > = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Targs... comps) + { + flecs::iter iter_wrapper(iter); + func(iter_wrapper, (column< + remove_reference_t< remove_pointer_t<Components> > >( + static_cast< remove_reference_t< + remove_pointer_t<Components> > *> + (comps.ptr), iter->count, comps.is_ref))...); + } + + template <typename... Targs, + if_t<sizeof...(Targs) != sizeof...(Components)> = 0> + static void invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Targs... comps) + { + invoke_callback(iter, func, index + 1, columns, comps..., + columns[index]); + } + + Func m_func; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility to invoke callback on entity if it has components in signature +//////////////////////////////////////////////////////////////////////////////// + +template<typename ... Args> +class entity_with_invoker_impl; + +template<typename ... Args> +class entity_with_invoker_impl<arg_list<Args ...>> { +public: + using ColumnArray = flecs::array<int32_t, sizeof...(Args)>; + using ConstPtrArray = flecs::array<const void*, sizeof...(Args)>; + using PtrArray = flecs::array<void*, sizeof...(Args)>; + using DummyArray = flecs::array<int, sizeof...(Args)>; + using IdArray = flecs::array<id_t, sizeof...(Args)>; + + template <typename ArrayType> + static bool get_ptrs(world& w, ecs_record_t *r, ecs_table_t *table, + ArrayType& ptrs) + { + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t type = ecs_table_get_type(table); + if (!type) { + return false; + } + + /* Get column indices for components */ + ColumnArray columns ({ + ecs_type_index_of(type, 0, w.id<Args>())... + }); + + /* Get pointers for columns for entity */ + size_t i = 0; + for (int32_t column : columns) { + if (column == -1) { + return false; + } + + ptrs[i ++] = ecs_record_get_column(r, column, 0); + } + + return true; + } + + template <typename ArrayType> + static bool get_mut_ptrs(world& w, ecs_entity_t e, ArrayType& ptrs) { + world_t *world = w.c_ptr(); + + /* Get pointers w/get_mut */ + size_t i = 0; + DummyArray dummy ({ + (ptrs[i ++] = ecs_get_mut_id(world, e, w.id<Args>(), NULL), 0)... + }); + + return true; + } + + template <typename Func> + static bool invoke_get(world_t *world, entity_t id, const Func& func) { + flecs::world w(world); + + ecs_record_t *r = ecs_record_find(world, id); + if (!r) { + return false; + } + + ecs_table_t *table = r->table; + if (!table) { + return false; + } + + ConstPtrArray ptrs; + if (!get_ptrs(w, r, table, ptrs)) { + return false; + } + + invoke_callback(func, 0, ptrs); + + return true; + } + + // 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 <typename Func> + static bool invoke_get_mut(world_t *world, entity_t id, const Func& func) { + flecs::world w(world); + + PtrArray ptrs; + + // 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. + + // Find table for entity + ecs_record_t *r = ecs_record_find(world, id); + ecs_table_t *table = NULL; + 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<Args>()), + elem = store_added(added, elem, prev, next, w.id<Args>()), + prev = next, 0 + )... }); + (void)dummy_before; + + // If table is different, move entity straight to it + if (table != next) { + ecs_ids_t ids; + ids.array = added.ptr(); + ids.count = static_cast<ecs_size_t>(elem); + ecs_commit(world, id, r, next, &ids, NULL); + table = next; + } + + if (!get_ptrs(w, r, table, ptrs)) { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + // When deferred, obtain pointers with regular get_mut + } else { + get_mut_ptrs(w, id, ptrs); + } + + invoke_callback(func, 0, ptrs); + + // Call modified on each component + DummyArray dummy_after ({ + ( ecs_modified_id(world, id, w.id<Args>()), 0)... + }); + (void)dummy_after; + + return true; + } + +private: + template <typename Func, typename ArrayType, typename ... TArgs, + if_t<sizeof...(TArgs) == sizeof...(Args)> = 0> + static void invoke_callback( + const Func& f, size_t, ArrayType&, TArgs&& ... comps) + { + f(*static_cast<typename base_arg_type<Args>::type*>(comps)...); + } + + template <typename Func, typename ArrayType, typename ... TArgs, + if_t<sizeof...(TArgs) != sizeof...(Args)> = 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 <typename Func, typename U = int> +class entity_with_invoker { + static_assert(function_traits<Func>::value, "type is not callable"); +}; + +template <typename Func> +class entity_with_invoker<Func, if_t< is_callable<Func>::value > > + : public entity_with_invoker_impl< arg_list_t<Func> > +{ + static_assert(function_traits<Func>::arity > 0, + "function must have at least one argument"); +}; + +} // namespace _ + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/iter.hpp b/fggl/ecs2/flecs/include/flecs/cpp/iter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2560a7c48332b02f15f1d60fd48561f656768f4f --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/iter.hpp @@ -0,0 +1,529 @@ + +namespace flecs +{ + +/** Unsafe wrapper class around a column. + * This class can be used when a system does not know the type of a column at + * compile time. + */ +class unsafe_column { +public: + unsafe_column(void* array, size_t size, size_t count, bool is_shared = false) + : m_array(array) + , m_size(size) + , m_count(count) + , m_is_shared(is_shared) {} + + /** Return element in component array. + * This operator may only be used if the column is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + void* operator[](size_t index) { + ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + ecs_assert(!m_is_shared, ECS_INVALID_PARAMETER, NULL); + return ECS_OFFSET(m_array, m_size * index); + } + + /** Return whether component is set. + * If the column is optional, this method may return false. + * + * @return True if component is set, false if component is not set. + */ + bool is_set() const { + return m_array != nullptr; + } + + /** Return whether component is shared. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_shared() const { + return m_is_shared; + } + +protected: + void* m_array; + size_t m_size; + size_t m_count; + bool m_is_shared; +}; + +/** Wrapper class around a column. + * + * @tparam T component type of the column. + */ +template <typename T> +class column { +public: + static_assert(std::is_empty<T>::value == false, + "invalid type for column, cannot iterate empty type"); + + /** Create column from component array. + * + * @param array Pointer to the component array. + * @param count Number of elements in component array. + * @param is_shared Is the component shared or not. + */ + column(T* array, size_t count, bool is_shared = false) + : m_array(array) + , m_count(count) + , m_is_shared(is_shared) {} + + /** Create column from iterator. + * + * @param iter Iterator object. + * @param column Index of the signature of the query being iterated over. + */ + column(iter &iter, int column); + + /** Return element in component array. + * This operator may only be used if the column is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + T& operator[](size_t index) { + ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + ecs_assert(!index || !m_is_shared, ECS_INVALID_PARAMETER, NULL); + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array[index]; + } + + /** Return first element of component array. + * This operator is typically used when the column is shared. + * + * @return Reference to the first element. + */ + T& operator*() { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return *m_array; + } + + /** Return first element of component array. + * This operator is typically used when the column is shared. + * + * @return Pointer to the first element. + */ + T* operator->() { + ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); + return m_array; + } + + /** Return whether component is set. + * If the column is optional, this method may return false. + * + * @return True if component is set, false if component is not set. + */ + bool is_set() const { + return m_array != nullptr; + } + + /** Return whether component is shared. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_shared() const { + return m_is_shared; + } + + /** Return whether component is owned. + * If the column is shared, this method returns true. + * + * @return True if component is shared, false if component is owned. + */ + bool is_owned() const { + return !m_is_shared; + } + +protected: + T* m_array; + size_t m_count; + bool m_is_shared; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +namespace _ { + +//////////////////////////////////////////////////////////////////////////////// + +/** Iterate over an integer range (used to iterate over entity range). + * + * @tparam Type of the iterator + */ +template <typename T> +class range_iterator +{ +public: + explicit range_iterator(T value) + : m_value(value){} + + bool operator!=(range_iterator const& other) const + { + return m_value != other.m_value; + } + + T const& operator*() const + { + return m_value; + } + + range_iterator& operator++() + { + ++m_value; + return *this; + } + +private: + T m_value; +}; + +} // namespace _ + +} // namespace flecs + +#ifdef FLECS_DEPRECATED +#include "../addons/deprecated/iter.hpp" +#else +namespace flecs +{ +template <typename Base> +class iter_deprecated { }; +} +#endif + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// + +/** Class that enables iterating over table columns. + */ +class iter : public iter_deprecated<iter> { + using row_iterator = _::range_iterator<size_t>; +public: + /** Construct iterator from C iterator object. + * This operation is typically not invoked directly by the user. + * + * @param it Pointer to C iterator. + */ + iter(const ecs_iter_t *it) : m_iter(it) { + m_begin = 0; + m_end = static_cast<std::size_t>(it->count); + } + + row_iterator begin() const { + return row_iterator(m_begin); + } + + row_iterator end() const { + return row_iterator(m_end); + } + + /** Obtain handle to current system. + */ + flecs::entity system() const; + + /** Obtain current world. + */ + flecs::world world() const; + + /** Obtain pointer to C iterator object + */ + const flecs::iter_t* c_ptr() const { + return m_iter; + } + + /** Number of entities to iterate over. + */ + size_t count() const { + return static_cast<size_t>(m_iter->count); + } + + /** Return delta_time of current frame. + */ + FLECS_FLOAT delta_time() const { + return m_iter->delta_time; + } + + /** Return time elapsed since last time system was invoked. + */ + FLECS_FLOAT delta_system_time() const { + return m_iter->delta_system_time; + } + + /** Return total time passed in simulation. + */ + FLECS_FLOAT world_time() const { + return m_iter->world_time; + } + + /** Obtain type of the entities being iterated over. + */ + flecs::type type() const; + + /** Is current type a module or does it contain module contents? */ + bool has_module() const { + return ecs_table_has_module(ecs_iter_table(m_iter)); + } + + /** Access self. + * 'self' is an entity that can be associated with a trigger, observer or + * system when they are created. */ + flecs::entity self() const; + + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + void* ctx() { + return m_iter->ctx; + } + + /** Access param. + * param contains the pointer passed to the param argument of system::run + */ + void* param() { + return m_iter->param; + } + + /** Obtain mutable handle to entity being iterated over. + * + * @param row Row being iterated over. + */ + flecs::entity entity(size_t row) const; + + /** Obtain the total number of inactive tables the query is matched with. + */ + int32_t inactive_table_count() const { + return m_iter->inactive_table_count; + } + + /** Returns whether term is owned. + * + * @param index The term index. + */ + bool is_owned(int32_t index) const { + return ecs_term_is_owned(m_iter, index); + } + + /** Returns whether term is set. + * + * @param index The term index. + */ + bool is_set(int32_t index) const { + return ecs_term_is_set(m_iter, index); + } + + /** Returns whether term is readonly. + * + * @param index The term index. + */ + bool is_readonly(int32_t index) const { + return ecs_term_is_readonly(m_iter, index); + } + + /** Number of terms in iteator. + */ + int32_t term_count() const { + return m_iter->column_count; + } + + /** Size of term data type. + * + * @param index The term id. + */ + size_t term_size(int32_t index) const { + return ecs_term_size(m_iter, index); + } + + /** Obtain term source (0 if self) + * + * @param index The term index. + */ + flecs::entity term_source(int32_t index) const; + + /** Obtain component/tag entity of term. + * + * @param index The term index. + */ + flecs::entity term_id(int32_t index) const; + + /** Obtain term with const type. + * If the specified term index does not match with the provided type, the + * function will assert. + * + * @tparam T Type of the term. + * @param index The term index. + * @return The term data. + */ + template <typename T, typename A = actual_type_t<T>, + typename std::enable_if<std::is_const<T>::value, void>::type* = nullptr> + + flecs::column<A> term(int32_t index) const { + return get_term<A>(index); + } + + /** Obtain term with non-const type. + * If the specified term id does not match with the provided type or if + * the term is readonly, the function will assert. + * + * @tparam T Type of the term. + * @param index The term index. + * @return The term data. + */ + template <typename T, typename A = actual_type_t<T>, + typename std::enable_if< + std::is_const<T>::value == false, void>::type* = nullptr> + + flecs::column<A> term(int32_t index) const { + ecs_assert(!ecs_term_is_readonly(m_iter, index), + ECS_COLUMN_ACCESS_VIOLATION, NULL); + return get_term<A>(index); + } + + /** Obtain unsafe term. + * Unsafe terms are required when a system does not know at compile time + * which component will be passed to it. + * + * @param index The term index. + */ + flecs::unsafe_column term(int32_t index) const { + return get_unsafe_term(index); + } + + /** Obtain owned term. + * Same as iter::term, but ensures that term is owned. + * + * @tparam Type of the term. + * @param index The term index. + * @return The term data. + */ + template <typename T, typename A = actual_type_t<T>> + flecs::column<A> term_owned(int32_t index) const { + ecs_assert(!!ecs_is_owned(m_iter, index), ECS_COLUMN_IS_SHARED, NULL); + return this->term<A>(index); + } + + /** Obtain shared term. + * Same as iter::term, but ensures that term is shared. + * + * @tparam Type of the term. + * @param index The term index. + * @return The component term. + */ + template <typename T, typename A = actual_type_t<T>> + const T& term_shared(int32_t index) const { + ecs_assert( + ecs_term_id(m_iter, index) == + _::cpp_type<T>::id(m_iter->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); + + ecs_assert(!ecs_term_is_owned(m_iter, index), + ECS_COLUMN_IS_NOT_SHARED, NULL); + + return *static_cast<A*>(ecs_term_w_size(m_iter, sizeof(A), index)); + } + + /** Obtain the total number of tables the iterator will iterate over. + */ + int32_t table_count() const { + return m_iter->table_count; + } + + /** Obtain untyped pointer to table column. + * + * @param table_column Id of table column (corresponds with location in table type). + * @return Pointer to table column. + */ + void* table_column(int32_t col) const { + return ecs_iter_column_w_size(m_iter, 0, col); + } + + /** Obtain typed pointer to table column. + * If the table does not contain a column with the specified type, the + * function will assert. + * + * @tparam T Type of the table column. + */ + template <typename T, typename A = actual_type_t<T>> + flecs::column<T> table_column() const { + auto col = ecs_iter_find_column(m_iter, _::cpp_type<T>::id()); + ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); + + return flecs::column<A>(static_cast<A*>(ecs_iter_column_w_size(m_iter, + sizeof(A), col)), static_cast<std::size_t>(m_iter->count), false); + } + + template <typename T> + flecs::column<T> table_column(flecs::id_t obj) const { + auto col = ecs_iter_find_column(m_iter, + ecs_pair(_::cpp_type<T>::id(), obj)); + ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); + + return flecs::column<T>(static_cast<T*>(ecs_iter_column_w_size(m_iter, + sizeof(T), col)), static_cast<std::size_t>(m_iter->count), false); + } + +private: + /* Get term, check if correct type is used */ + template <typename T, typename A = actual_type_t<T>> + flecs::column<T> get_term(int32_t index) const { + +#ifndef NDEBUG + ecs_entity_t term_id = ecs_term_id(m_iter, index); + ecs_assert(term_id & ECS_PAIR || term_id & ECS_SWITCH || + term_id & ECS_CASE || + term_id == _::cpp_type<T>::id(m_iter->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif + + size_t count; + bool is_shared = !ecs_term_is_owned(m_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<size_t>(m_iter->count); + } + + return flecs::column<A>( + static_cast<T*>(ecs_term_w_size(m_iter, sizeof(A), index)), + count, is_shared); + } + + flecs::unsafe_column get_unsafe_term(int32_t index) const { + size_t count; + size_t size = ecs_term_size(m_iter, index); + bool is_shared = !ecs_term_is_owned(m_iter, index); + + /* 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<size_t>(m_iter->count); + } + + return flecs::unsafe_column( + ecs_term_w_size(m_iter, 0, index), size, count, is_shared); + } + + const flecs::iter_t *m_iter; + std::size_t m_begin; + std::size_t m_end; +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/lifecycle_traits.hpp b/fggl/ecs2/flecs/include/flecs/cpp/lifecycle_traits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f3104e1e22d7aace47d50fc59941a17f95e753ea --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/lifecycle_traits.hpp @@ -0,0 +1,447 @@ +namespace flecs +{ + +namespace _ +{ + +inline void ecs_ctor_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot default construct %s, add %s::%s() or use emplace<T>", + path, path, ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_dtor_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, "cannnot destruct %s, add ~%s::%s()", + path, path, ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_copy_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + const ecs_entity_t*, void *, const void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot copy assign %s, add %s& %s::operator =(const %s&)", path, + ecs_get_name(w, id), path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_move_illegal(ecs_world_t* w, ecs_entity_t id, const ecs_entity_t*, + const ecs_entity_t*, void *, void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot move assign %s, add %s& %s::operator =(%s&&)", path, + ecs_get_name(w, id), path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_copy_ctor_illegal(ecs_world_t* w, ecs_entity_t id, + const EcsComponentLifecycle*, const ecs_entity_t*, const ecs_entity_t*, + void *, const void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot copy construct %s, add %s::%s(const %s&)", + path, path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + +inline void ecs_move_ctor_illegal(ecs_world_t* w, ecs_entity_t id, + const EcsComponentLifecycle*, const ecs_entity_t*, const ecs_entity_t*, + void *, void *, size_t, int32_t, void*) +{ + char *path = ecs_get_path_w_sep(w, 0, id, "::", "::"); + ecs_abort(ECS_INVALID_OPERATION, + "cannnot move construct %s, add %s::%s(%s&&)", + path, path, ecs_get_name(w, id), ecs_get_name(w, id)); + ecs_os_free(path); +} + + +// T() +// Can't coexist with T(flecs::entity) or T(flecs::world, flecs::entity) +template <typename T> +void ctor_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, void *ptr, size_t size, + int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast<T*>(ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&arr[i], T); + } +} + +// T(flecs::world, flecs::entity) +template <typename T> +void ctor_world_entity_impl( + ecs_world_t* world, ecs_entity_t, const ecs_entity_t* ids, void *ptr, + size_t size, int32_t count, void*); + +// ~T() +template <typename T> +void dtor_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, void *ptr, size_t size, + int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast<T*>(ptr); + for (int i = 0; i < count; i ++) { + arr[i].~T(); + } +} + +// T& operator=(const T&) +template <typename T> +void copy_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, const ecs_entity_t*, + void *dst_ptr, const void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + const T *src_arr = static_cast<const T*>(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = src_arr[i]; + } +} + +// T& operator=(T&&) +template <typename T> +void move_impl( + ecs_world_t*, ecs_entity_t, const ecs_entity_t*, const ecs_entity_t*, + void *dst_ptr, void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = std::move(src_arr[i]); + } +} + +// T(T&) +template <typename T> +void copy_ctor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + const void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + const T *src_arr = static_cast<const T*>(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(src_arr[i])); + } +} + +// T(T&&) +template <typename T> +void move_ctor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(std::move(src_arr[i]))); + } +} + +// T(T&&), ~T() +// Typically used when moving to a new table, and removing from the old table +template <typename T> +void ctor_move_dtor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(std::move(src_arr[i]))); + src_arr[i].~T(); + } +} + +// Move assign + dtor (non-trivial move assigmnment) +// Typically used when moving a component to a deleted component +template <typename T, if_not_t< + std::is_trivially_move_assignable<T>::value > = 0> +void move_dtor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + // Move assignment should free dst & assign dst to src + dst_arr[i] = std::move(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 assigmnment) +// Typically used when moving a component to a deleted component +template <typename T, if_t< + std::is_trivially_move_assignable<T>::value > = 0> +void move_dtor_impl( + ecs_world_t*, ecs_entity_t, const EcsComponentLifecycle*, + const ecs_entity_t*, const ecs_entity_t*, void *dst_ptr, + void *src_ptr, size_t size, int32_t count, void*) +{ + (void)size; ecs_assert(size == sizeof(T), ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast<T*>(dst_ptr); + T *src_arr = static_cast<T*>(src_ptr); + for (int i = 0; i < count; i ++) { + // Cleanup resources of dst + dst_arr[i].~T(); + // Copy src to dst + dst_arr[i] = std::move(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 has flecs constructor +template <typename T> +struct has_flecs_ctor { + static constexpr bool value = + std::is_constructible<actual_type_t<T>, + flecs::world&, flecs::entity>::value; +}; + +// Trait to test if type is constructible by flecs +template <typename T> +struct is_flecs_constructible { + static constexpr bool value = + std::is_default_constructible<actual_type_t<T>>::value || + std::is_constructible<actual_type_t<T>, + flecs::world&, flecs::entity>::value; +}; + +// Trait to test if type has a self constructor (flecs::entity, Args...) +template <typename T, typename ... Args> +struct is_self_constructible { + static constexpr bool value = + std::is_constructible<actual_type_t<T>, + flecs::entity, Args...>::value; +}; + +namespace _ +{ + +// Trivially constructible +template <typename T, if_t< std::is_trivially_constructible<T>::value > = 0> +ecs_xtor_t ctor() { + return nullptr; +} + +// Not constructible by flecs +template <typename T, if_t< + ! std::is_default_constructible<T>::value && + ! has_flecs_ctor<T>::value > = 0> +ecs_xtor_t ctor() { + return ecs_ctor_illegal; +} + +// Default constructible +template <typename T, if_t< + ! std::is_trivially_constructible<T>::value && + std::is_default_constructible<T>::value && + ! has_flecs_ctor<T>::value > = 0> +ecs_xtor_t ctor() { + return ctor_impl<T>; +} + +// Flecs constructible: T(flecs::world, flecs::entity) +template <typename T, if_t< has_flecs_ctor<T>::value > = 0> +ecs_xtor_t ctor() { + return ctor_world_entity_impl<T>; +} + +// No dtor +template <typename T, if_t< std::is_trivially_destructible<T>::value > = 0> +ecs_xtor_t dtor() { + return nullptr; +} + +// Dtor +template <typename T, if_t< + std::is_destructible<T>::value && + ! std::is_trivially_destructible<T>::value > = 0> +ecs_xtor_t dtor() { + return dtor_impl<T>; +} + +// Assert when the type cannot be destructed +template <typename T, if_not_t< std::is_destructible<T>::value > = 0> +ecs_xtor_t dtor() { + flecs_static_assert(always_false<T>::value, + "component type must be destructible"); + return ecs_dtor_illegal; +} + +// Trivially copyable +template <typename T, if_t< std::is_trivially_copyable<T>::value > = 0> +ecs_copy_t copy() { + return nullptr; +} + +// Not copyable +template <typename T, if_t< + ! std::is_trivially_copyable<T>::value && + ! std::is_copy_assignable<T>::value > = 0> +ecs_copy_t copy() { + return ecs_copy_illegal; +} + +// Copy assignment +template <typename T, if_t< + std::is_copy_assignable<T>::value && + ! std::is_trivially_copyable<T>::value > = 0> +ecs_copy_t copy() { + return copy_impl<T>; +} + +// Trivially move assignable +template <typename T, if_t< std::is_trivially_move_assignable<T>::value > = 0> +ecs_move_t move() { + return nullptr; +} + +// Component types must be move assignable +template <typename T, if_not_t< std::is_move_assignable<T>::value > = 0> +ecs_move_t move() { + flecs_static_assert(always_false<T>::value, + "component type must be move assignable"); + return ecs_move_illegal; +} + +// Move assignment +template <typename T, if_t< + std::is_move_assignable<T>::value && + ! std::is_trivially_move_assignable<T>::value > = 0> +ecs_move_t move() { + return move_impl<T>; +} + +// Trivially copy constructible +template <typename T, if_t< + std::is_trivially_copy_constructible<T>::value > = 0> +ecs_copy_ctor_t copy_ctor() { + return nullptr; +} + +// No copy ctor +template <typename T, if_t< ! std::is_copy_constructible<T>::value > = 0> +ecs_copy_ctor_t copy_ctor() { + return ecs_copy_ctor_illegal; +} + +// Copy ctor +template <typename T, if_t< + std::is_copy_constructible<T>::value && + ! std::is_trivially_copy_constructible<T>::value > = 0> +ecs_copy_ctor_t copy_ctor() { + return copy_ctor_impl<T>; +} + +// Trivially move constructible +template <typename T, if_t< + std::is_trivially_move_constructible<T>::value > = 0> +ecs_move_ctor_t move_ctor() { + return nullptr; +} + +// Component types must be move constructible +template <typename T, if_not_t< std::is_move_constructible<T>::value > = 0> +ecs_move_ctor_t move_ctor() { + flecs_static_assert(always_false<T>::value, + "component type must be move constructible"); + return ecs_move_ctor_illegal; +} + +// Move ctor +template <typename T, if_t< + std::is_move_constructible<T>::value && + ! std::is_trivially_move_constructible<T>::value > = 0> +ecs_move_ctor_t move_ctor() { + return move_ctor_impl<T>; +} + +// Trivial merge (move assign + dtor) +template <typename T, if_t< + std::is_trivially_move_constructible<T>::value && + std::is_trivially_destructible<T>::value > = 0> +ecs_move_ctor_t ctor_move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template <typename T, if_t< + ! std::is_move_constructible<T>::value || + ! std::is_destructible<T>::value > = 0> +ecs_move_ctor_t ctor_move_dtor() { + flecs_static_assert(always_false<T>::value, + "component type must be move constructible and destructible"); + return ecs_move_ctor_illegal; +} + +// Merge ctor + dtor +template <typename T, if_t< + !(std::is_trivially_move_constructible<T>::value && + std::is_trivially_destructible<T>::value) && + std::is_move_constructible<T>::value && + std::is_destructible<T>::value > = 0> +ecs_move_ctor_t ctor_move_dtor() { + return ctor_move_dtor_impl<T>; +} + +// Trivial merge (move assign + dtor) +template <typename T, if_t< + std::is_trivially_move_assignable<T>::value && + std::is_trivially_destructible<T>::value > = 0> +ecs_move_ctor_t move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template <typename T, if_t< + ! std::is_move_assignable<T>::value || + ! std::is_destructible<T>::value > = 0> +ecs_move_ctor_t move_dtor() { + flecs_static_assert(always_false<T>::value, + "component type must be move constructible and destructible"); + return ecs_move_ctor_illegal; +} + +// Merge assign + dtor +template <typename T, if_t< + !(std::is_trivially_move_assignable<T>::value && + std::is_trivially_destructible<T>::value) && + std::is_move_assignable<T>::value && + std::is_destructible<T>::value > = 0> +ecs_move_ctor_t move_dtor() { + return move_dtor_impl<T>; +} + +} // _ +} // flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/module.hpp b/fggl/ecs2/flecs/include/flecs/cpp/module.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3a591f3ef0c10449db8c9309db25e87fb8948735 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/module.hpp @@ -0,0 +1,101 @@ + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Define a module +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +flecs::entity module(const flecs::world& world, const char *name = nullptr) { + ecs_set_scope(world.c_ptr(), 0); + flecs::entity result = pod_component<T>(world, name, false); + ecs_add_module_tag(world, result.id()); + ecs_set_scope(world.c_ptr(), result.id()); + + // Only register copy/move/dtor, make sure to not instantiate ctor as the + // default ctor doesn't work for modules. Additionally, the module ctor + // should only be invoked once per import. + EcsComponentLifecycle cl{}; + cl.copy = _::copy<T>(); + cl.move = _::move<T>(); + cl.dtor = _::dtor<T>(); + ecs_set_component_actions_w_entity(world, result, &cl); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Import a module +//////////////////////////////////////////////////////////////////////////////// + +template <typename T> +ecs_entity_t do_import(world& world, const char *symbol) { + ecs_trace_1("import %s", _::name_helper<T>::name()); + ecs_log_push(); + + ecs_entity_t scope = ecs_get_scope(world); + + // Create custom storage to prevent object destruction + T* module_data = static_cast<T*>(ecs_os_malloc(sizeof(T))); + FLECS_PLACEMENT_NEW(module_data, T(world)); + + ecs_set_scope(world, scope); + + // It should now be possible to lookup the module + ecs_entity_t m = ecs_lookup_symbol(world, symbol, true); + ecs_assert(m != 0, ECS_MODULE_UNDEFINED, symbol); + + _::cpp_type<T>::init(world, m, false); + + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INTERNAL_ERROR, NULL); + + // Set module singleton component + + T* module_ptr = static_cast<T*>( + ecs_get_mut_id(world, m, + _::cpp_type<T>::id_explicit(world, nullptr, false), NULL)); + + *module_ptr = std::move(*module_data); + + // Don't dtor, as a module should only be destructed once when the module + // component is removed. + ecs_os_free(module_data); + + // Add module tag + ecs_add_id(world, m, flecs::Module); + + ecs_log_pop(); + + return m; +} + +template <typename T> +flecs::entity import(world& world) { + char *symbol = _::symbol_helper<T>::symbol(); + + ecs_entity_t m = ecs_lookup_symbol(world.c_ptr(), symbol, true); + + if (!_::cpp_type<T>::registered()) { + + /* Module is registered with world, initialize static data */ + if (m) { + _::cpp_type<T>::init(world.c_ptr(), m, false); + + /* Module is not yet registered, register it now */ + } else { + m = do_import<T>(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<T>(world, symbol); + } + + ecs_os_free(symbol); + + return flecs::entity(world, m); +} + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/observer.hpp b/fggl/ecs2/flecs/include/flecs/cpp/observer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ab27a3762409822c115dae7c306b9c8ec51482e8 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/observer.hpp @@ -0,0 +1,27 @@ + +namespace flecs +{ + +template<typename ... Components> +class observer : public entity +{ +public: + explicit observer() + : entity() { } + + explicit observer(flecs::world_t *world, flecs::entity_t id) + : entity(world, id) { } + + void ctx(void *ctx) { + ecs_observer_desc_t desc = {}; + desc.entity.entity = m_id; + desc.ctx = ctx; + ecs_observer_init(m_world, &desc); + } + + void* ctx() const { + return ecs_get_observer_ctx(m_world, m_id); + } +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/pair.hpp b/fggl/ecs2/flecs/include/flecs/cpp/pair.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d0a573faae847e8e52e2b7db34509662e27de74e --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/pair.hpp @@ -0,0 +1,120 @@ + +namespace flecs { + +namespace _ { + struct pair_base { }; +} // _ + + +// Type that represents a pair and can encapsulate a temporary value +template <typename R, typename O> +struct pair : _::pair_base { + // Traits used to deconstruct the pair + + // The actual type of the pair is determined by which type of the pair is + // empty. If both types are empty or not empty, the pair assumes the type + // of the relation. + using type = conditional_t<!is_empty<R>::value || is_empty<O>::value, R, O>; + using relation = R; + using object = O; + + pair(type& v) : ref_(v) { } + + // This allows the class to be used as a temporary object + pair(const type& v) : ref_(const_cast<type&>(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 <typename R, typename O, if_t<is_empty<R>::value> = 0> +using pair_object = pair<R, O>; + + +// Utilities to test if type is a pair +template <typename T> +struct is_pair { + static constexpr bool value = is_base_of<_::pair_base, remove_reference_t<T> >::value; +}; + + +// Get actual type, relation or object from pair while preserving cv qualifiers. +template <typename P> +using pair_relation_t = transcribe_cv_t<remove_reference_t<P>, typename remove_reference_t<P>::relation>; + +template <typename P> +using pair_object_t = transcribe_cv_t<remove_reference_t<P>, typename remove_reference_t<P>::object>; + +template <typename P> +using pair_type_t = transcribe_cv_t<remove_reference_t<P>, typename remove_reference_t<P>::type>; + + +// Get actual type from a regular type or pair +template <typename T, typename U = int> +struct actual_type; + +template <typename T> +struct actual_type<T, if_not_t< is_pair<T>::value >> { + using type = T; +}; + +template <typename T> +struct actual_type<T, if_t< is_pair<T>::value >> { + using type = pair_type_t<T>; +}; + +template <typename T> +using actual_type_t = typename actual_type<T>::type; + + +// Get type without const, *, & +template<typename T> +struct base_type { + using type = remove_pointer_t< decay_t< actual_type_t<T> > >; +}; + +template <typename T> +using base_type_t = typename base_type<T>::type; + + +// Get type without *, & (retains const which is useful for function args) +template<typename T> +struct base_arg_type { + using type = remove_pointer_t< remove_reference_t< actual_type_t<T> > >; +}; + +template <typename T> +using base_arg_type_t = typename base_arg_type<T>::type; + + +// Test if type is the same as its actual type +template <typename T> +struct is_actual { + static constexpr bool value = std::is_same<T, actual_type_t<T> >::value; +}; + +} // flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/query.hpp b/fggl/ecs2/flecs/include/flecs/cpp/query.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b80ddd442c99c5c3dfb32ce0f09cc8d56147f038 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/query.hpp @@ -0,0 +1,242 @@ + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Persistent queries +//////////////////////////////////////////////////////////////////////////////// + +class query_base { +public: + query_base() + : m_world(nullptr) + , m_query(nullptr) { } + + query_base(world_t *world, query_t *query = nullptr) + : m_world(world) + , m_query(query) { } + + /** Get pointer to C query object. + */ + query_t* c_ptr() const { + return m_query; + } + + /** 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 (sub)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * 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 <typename T> + void order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_query_order_by(m_world, m_query, + flecs::_::cpp_type<T>::id(m_world), + reinterpret_cast<ecs_order_by_action_t>(compare)); + } + + /** Sort the output of a query. + * Same as order_by<T>, but with component identifier. + * + * @param component The component used to sort. + * @param compare The compare function used to sort the components. + */ + void order_by(flecs::entity component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + ecs_query_order_by(m_world, m_query, component.id(), compare); + } + + /** Group and sort matched tables. + * Similar yo ecs_query_order_by, but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * 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 rank The rank action. + */ + template <typename T> + void group_by(ecs_group_by_action_t callback) { + ecs_query_group_by(m_world, m_query, + flecs::_::cpp_type<T>::id(m_world), callback); + } + + /** Group and sort matched tables. + * Same as group_by<T>, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param rank The rank action. + */ + void group_by(flecs::entity component, ecs_group_by_action_t callback) { + ecs_query_group_by(m_world, m_query, component.id(), callback); + } + + /** Returns whether the query data changed since the last iteration. + * This operation must be invoked before obtaining the iterator, as this will + * reset the changed state. The operation will return true after: + * - new entities have been matched with + * - matched entities were deleted + * - matched components were changed + * + * @return true if entities changed, otherwise false. + */ + bool changed() { + return ecs_query_changed(m_query); + } + + /** Returns whether query is orphaned. + * When the parent query of a subquery is deleted, it is left in an orphaned + * state. The only valid operation on an orphaned query is deleting it. Only + * subqueries can be orphaned. + * + * @return true if query is orphaned, otherwise false. + */ + bool orphaned() { + return ecs_query_orphaned(m_query); + } + + /** Free the query. + */ + void destruct() { + ecs_query_free(m_query); + m_world = nullptr; + m_query = nullptr; + } + + template <typename Func> + void iter(Func&& func) const { + ecs_iter_t it = ecs_query_iter(m_query); + while (ecs_query_next(&it)) { + _::iter_invoker<Func>(func).invoke(&it); + } + } + + template <typename Func> + void each_term(const Func& func) { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); + + for (int i = 0; i < f->term_count; i ++) { + flecs::term t(m_world, f->terms[i]); + func(t); + } + } + + flecs::term term(int32_t index) { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs::term(m_world, f->terms[index]); + } + + int32_t term_count() { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + return f->term_count; + } + + flecs::string str() { + const ecs_filter_t *f = ecs_query_get_filter(m_query); + char *result = ecs_filter_str(m_world, f); + return flecs::string(result); + } + +protected: + world_t *m_world; + query_t *m_query; +}; + + +template<typename ... Components> +class query : public query_base { + using Terms = typename _::term_ptrs<Components...>::array; + +public: + query() { } + + query(world_t *world, query_t *q) + : query_base(world, q) { } + + explicit query(const world& world, const char *expr = nullptr) + : query_base(world.c_ptr()) + { + auto qb = world.query_builder<Components ...>() + .expr(expr); + + if (!expr) { + qb.substitute_default(); + } + + m_query = qb; + } + + explicit query(const world& world, query_base& parent, const char *expr = nullptr) + : query_base(world.c_ptr()) + { + auto qb = world.query_builder<Components ...>() + .parent(parent) + .expr(expr); + + if (!expr) { + qb.substitute_default(); + } + + m_query = qb; + } + + template <typename Func> + void each(Func&& func) const { + iterate<_::each_invoker>(std::forward<Func>(func), ecs_query_next); + } + + template <typename Func> + void each_worker(int32_t stage_current, int32_t stage_count, Func&& func) const { + iterate<_::each_invoker>(std::forward<Func>(func), + ecs_query_next_worker, stage_current, stage_count); + } + + template <typename Func> + void iter(Func&& func) const { + iterate<_::iter_invoker>(std::forward<Func>(func), ecs_query_next); + } + + template <typename Func> + void iter_worker(int32_t stage_current, int32_t stage_count, Func&& func) const { + iterate<_::iter_invoker>(std::forward<Func>(func), + ecs_query_next_worker, stage_current, stage_count); + } + + template <typename Func> + ECS_DEPRECATED("use each or iter") + void action(Func&& func) const { + iterate<_::action_invoker>(std::forward<Func>(func), ecs_query_next); + } + +private: + template < template<typename Func, typename ... Comps> class Invoker, typename Func, typename NextFunc, typename ... Args> + void iterate(Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = ecs_query_iter(m_query); + while (next(&it, std::forward<Args>(args)...)) { + Invoker<Func, Components...>(func).invoke(&it); + } + } +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/snapshot.hpp b/fggl/ecs2/flecs/include/flecs/cpp/snapshot.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b84b0849a0867f207fc28153706a904bee057b41 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/snapshot.hpp @@ -0,0 +1,88 @@ + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Snapshots make a copy of the world state that can be restored +//////////////////////////////////////////////////////////////////////////////// + +class snapshot final { +public: + explicit snapshot(const world& world) + : m_world( world ) + , m_snapshot( nullptr ) { } + + snapshot(const snapshot& obj) + : m_world( obj.m_world ) + { + ecs_iter_t it = ecs_snapshot_iter(obj.m_snapshot, nullptr); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + } + + snapshot(snapshot&& obj) + : m_world(obj.m_world) + , m_snapshot(obj.m_snapshot) + { + obj.m_snapshot = nullptr; + } + + snapshot& operator=(const snapshot& obj) { + ecs_assert(m_world.c_ptr() == obj.m_world.c_ptr(), ECS_INVALID_PARAMETER, NULL); + ecs_iter_t it = ecs_snapshot_iter(obj.m_snapshot, nullptr); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + return *this; + } + + snapshot& operator=(snapshot&& obj) { + ecs_assert(m_world.c_ptr() == obj.m_world.c_ptr(), ECS_INVALID_PARAMETER, NULL); + m_snapshot = obj.m_snapshot; + obj.m_snapshot = nullptr; + return *this; + } + + void take() { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + m_snapshot = ecs_snapshot_take(m_world.c_ptr()); + } + + template <typename F> + void take(const F& f) { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + + ecs_iter_t it = ecs_filter_iter(m_world, f.c_ptr()); + + printf("filter = %s\n", ecs_filter_str(m_world, f.c_ptr())); + m_snapshot = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + } + + void restore() { + if (m_snapshot) { + ecs_snapshot_restore(m_world.c_ptr(), m_snapshot); + m_snapshot = nullptr; + } + } + + ~snapshot() { + if (m_snapshot) { + ecs_snapshot_free(m_snapshot); + } + } + + snapshot_t* c_ptr() const { + return m_snapshot; + } + + filter_iterator begin(); + + filter_iterator end(); +private: + const world& m_world; + snapshot_t *m_snapshot; +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/system.hpp b/fggl/ecs2/flecs/include/flecs/cpp/system.hpp new file mode 100644 index 0000000000000000000000000000000000000000..18502b2b9b03689cde3fa2db99636b1e8deb6a77 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/system.hpp @@ -0,0 +1,223 @@ + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// Fluent interface to run a system manually +//////////////////////////////////////////////////////////////////////////////// + +class system_runner_fluent { +public: + system_runner_fluent( + world_t *world, + entity_t id, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + void *param) + : m_stage(world) + , m_id(id) + , m_delta_time(delta_time) + , m_param(param) + , m_filter() + , m_offset(0) + , m_limit(0) + , m_stage_current(stage_current) + , m_stage_count(stage_count) { } + + template <typename F> + system_runner_fluent& filter(const F& f) { + m_filter = f; + return *this; + } + + system_runner_fluent& offset(int32_t offset) { + m_offset = offset; + return *this; + } + + system_runner_fluent& limit(int32_t limit) { + m_limit = limit; + return *this; + } + + system_runner_fluent& stage(flecs::world& stage) { + m_stage = stage.c_ptr(); + return *this; + } + + ~system_runner_fluent() { + if (m_stage_count) { + ecs_run_worker( + m_stage, m_id, m_stage_current, m_stage_count, m_delta_time, + m_param); + } else { + ecs_run_w_filter( + m_stage, m_id, m_delta_time, m_offset, m_limit, + m_filter.c_ptr(), m_param); + } + } + +private: + world_t *m_stage; + entity_t m_id; + FLECS_FLOAT m_delta_time; + void *m_param; + flecs::filter<> m_filter; + int32_t m_offset; + int32_t m_limit; + int32_t m_stage_current; + int32_t m_stage_count; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Register a system with Flecs +//////////////////////////////////////////////////////////////////////////////// + +template<typename ... Components> +class system : public entity +{ +public: + explicit system() + : entity() { } + + explicit system(flecs::world_t *world, flecs::entity_t id) + : entity(world, id) { } + + template <typename T> + void order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + this->order_by(flecs::_::cpp_type<T>::id(m_world), + reinterpret_cast<ecs_order_by_action_t>(compare)); + } + + void order_by(flecs::entity_t comp, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + ecs_query_t *q = query().c_ptr(); + ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_order_by(m_world, q, comp, compare); + } + + template <typename T> + void group_by(int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + this->group_by(flecs::_::cpp_type<T>::id(m_world), rank); + } + + void group_by(flecs::entity_t comp, int(*rank)(flecs::world_t*, flecs::entity_t, flecs::type_t type)) { + ecs_query_t *q = query().c_ptr(); + ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_group_by(m_world, q, comp, rank); + } + + /** 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. + */ + void interval(FLECS_FLOAT interval) { + ecs_set_interval(m_world, m_id, interval); + } + + /** 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. + */ + void rate(const flecs::entity& tick_source, int32_t rate) { + ecs_set_rate(m_world, m_id, rate, tick_source.id()); + } + + /** 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. + */ + void rate(int32_t rate) { + ecs_set_rate(m_world, m_id, rate, 0); + } + + /** Get interval. + * Get interval at which the system is running. + * + * @return The timer entity. + */ + FLECS_FLOAT interval() { + return ecs_get_interval(m_world, m_id); + } + + void enable() { + ecs_enable(m_world, m_id, true); + } + + void disable() { + ecs_enable(m_world, m_id, false); + } + + void ctx(void *ctx) { + if (ecs_has(m_world, m_id, EcsSystem)) { + ecs_system_desc_t desc = {}; + desc.entity.entity = m_id; + desc.ctx = ctx; + ecs_system_init(m_world, &desc); + } else { + ecs_trigger_desc_t desc = {}; + desc.entity.entity = m_id; + desc.ctx = ctx; + ecs_trigger_init(m_world, &desc); + } + } + + void* ctx() const { + if (ecs_has(m_world, m_id, EcsSystem)) { + return ecs_get_system_ctx(m_world, m_id); + } else { + return ecs_get_trigger_ctx(m_world, m_id); + } + } + + ECS_DEPRECATED("use interval") + void period(FLECS_FLOAT period) { + this->interval(period); + } + + ECS_DEPRECATED("use interval") + void set_period(FLECS_FLOAT period) const { + this->interval(period); + } + + ECS_DEPRECATED("use ctx(void*)") + void set_context(void *ptr) { + ctx(ptr); + } + + ECS_DEPRECATED("use void* ctx()") + void* get_context() const { + return ctx(); + } + + query_base query() const { + return query_base(m_world, ecs_get_system_query(m_world, m_id)); + } + + system_runner_fluent run(FLECS_FLOAT delta_time = 0.0f, void *param = nullptr) const { + return system_runner_fluent(m_world, m_id, 0, 0, delta_time, param); + } + + system_runner_fluent run_worker( + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time = 0.0f, + void *param = nullptr) const + { + return system_runner_fluent( + m_world, m_id, stage_current, stage_count, delta_time, param); + } +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/type.hpp b/fggl/ecs2/flecs/include/flecs/cpp/type.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6925a933c4ca8e376a0d3ca1340063096622508a --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/type.hpp @@ -0,0 +1,205 @@ + +#ifdef FLECS_DEPRECATED +#include "../addons/deprecated/type.hpp" +#else +template <typename Base> +class type_deprecated { }; +#endif + +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// +//// A collection of component ids used to describe the contents of a table +//////////////////////////////////////////////////////////////////////////////// + +template <typename Base> +class type_base : public type_deprecated<type> { +public: + explicit type_base( + world_t *world, const char *name = nullptr, const char *expr = nullptr) + { + ecs_type_desc_t desc = {}; + desc.entity.name = name; + desc.ids_expr = expr; + m_entity = flecs::entity(world, ecs_type_init(world, &desc)); + sync_from_flecs(); + } + + explicit type_base(world_t *world, type_t t) + : m_entity( world, static_cast<flecs::id_t>(0) ) + , m_type( t ) + , m_normalized( t ) { } + + type_base(type_t t) + : m_type( t ) + , m_normalized( t ) { } + + Base& add(const Base& t) { + m_type = ecs_type_add(world(), m_type, t.id()); + m_normalized = ecs_type_merge(world(), m_normalized, t, nullptr); + sync_from_me(); + return *this; + } + + Base& add(id_t e) { + m_type = ecs_type_add(world(), m_type, e); + m_normalized = ecs_type_add(world(), m_normalized, e); + sync_from_me(); + return *this; + } + + template <typename T> + Base& add() { + return this->add(_::cpp_type<T>::id(world())); + } + + Base& add(entity_t relation, entity_t object) { + return this->add(ecs_pair(relation, object)); + } + + template <typename Relation, typename Object> + Base& add() { + return this->add<Relation>(_::cpp_type<Object>::id(world())); + } + + Base& is_a(entity_t object) { + return this->add(flecs::IsA, object); + } + + Base& child_of(entity_t object) { + return this->add(flecs::ChildOf, object); + } + + template <typename Relation> + Base& add(entity_t object) { + return this->add(_::cpp_type<Relation>::id(world()), object); + } + + template <typename Object> + Base& add_w_object(entity_t relation) { + return this->add(relation, _::cpp_type<Object>::id(world())); + } + + bool has(id_t id) { + return ecs_type_has_id(world(), m_normalized, id, false); + } + + bool has(id_t relation, id_t object) { + return ecs_type_has_id(world(), m_normalized, + ecs_pair(relation, object), false); + } + + template <typename T> + bool has() { + return this->has(_::cpp_type<T>::id(world())); + } + + template <typename Relation, typename Object> + bool has() { + return this->has(_::cpp_type<flecs::pair<Relation, Object>>::id(world())); + } + + template <typename T> + Base& component() { + component_for_id<T>(world(), m_entity); + return *this; + } + + flecs::string str() const { + char *str = ecs_type_str(world(), m_type); + return flecs::string(str); + } + + type_t c_ptr() const { + return m_type; + } + + flecs::id_t id() const { + return m_entity.id(); + } + + flecs::entity entity() const { + return m_entity; + } + + flecs::world world() const { + return m_entity.world(); + } + + type_t c_normalized() const { + return m_normalized; + } + + void enable() const { + ecs_enable(world(), id(), true); + } + + void disable() const { + ecs_enable(world(), id(), false); + } + + flecs::vector<flecs::id_t> vector() { + return flecs::vector<flecs::id_t>( const_cast<ecs_vector_t*>(m_normalized)); + } + + flecs::id get(int32_t index) { + return flecs::id(world(), vector().get(index)); + } + + /* Implicit conversion to type_t */ + operator type_t() const { return m_normalized; } + + operator Base&() { return *static_cast<Base*>(this); } + +private: + void sync_from_me() { + if (!id()) { + return; + } + + EcsType *tc = ecs_get_mut(world(), id(), EcsType, NULL); + ecs_assert(tc != NULL, ECS_INTERNAL_ERROR, NULL); + tc->type = m_type; + tc->normalized = m_normalized; + ecs_modified(world(), id(), EcsType); + + } + + void sync_from_flecs() { + if (!id()) { + return; + } + + EcsType *tc = ecs_get_mut(world(), id(), EcsType, NULL); + ecs_assert(tc != NULL, ECS_INTERNAL_ERROR, NULL); + m_type = tc->type; + m_normalized = tc->normalized; + ecs_modified(world(), id(), EcsType); + } + + flecs::entity m_entity; + type_t m_type; + type_t m_normalized; +}; + +class type : public type_base<type> { +public: + explicit type( + world_t *world, const char *name = nullptr, const char *expr = nullptr) + : type_base(world, name, expr) { } + + explicit type(world_t *world, type_t t) : type_base(world, t) { } + + type(type_t t) : type_base(t) { } +}; + +class pipeline : public type_base<pipeline> { +public: + explicit pipeline(world_t *world, const char *name) : type_base(world, name) + { + this->entity().add(flecs::Pipeline); + } +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/util.hpp b/fggl/ecs2/flecs/include/flecs/cpp/util.hpp new file mode 100644 index 0000000000000000000000000000000000000000..067f53bfb9e5973d1a49e92e6d8bb25de60fa633 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/util.hpp @@ -0,0 +1,380 @@ +//////////////////////////////////////////////////////////////////////////////// +//// 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) + +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<class Ty> inline void destruct_obj(Ty* _ptr) { _ptr->~Ty(); } +template<class Ty> inline void free_obj(Ty* _ptr) { + if (_ptr) { + destruct_obj(_ptr); + ecs_os_free(_ptr); + } +} + +} // namespace _ + +} // namespace flecs + +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 <bool V, typename T, typename F> +using conditional_t = typename std::conditional<V, T, F>::type; + +template <typename T> +using decay_t = typename std::decay<T>::type; + +template <bool V, typename T = void> +using enable_if_t = typename std::enable_if<V, T>::type; + +template <typename T> +using remove_pointer_t = typename std::remove_pointer<T>::type; + +template <typename T> +using remove_reference_t = typename std::remove_reference<T>::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; + + +// 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<class Src, class Dst> +using transcribe_const_t = conditional_t<is_const<Src>::value, Dst const, Dst>; + +template<class Src, class Dst> +using transcribe_volatile_t = conditional_t<is_volatile<Src>::value, Dst volatile, Dst>; + +template<class Src, class Dst> +using transcribe_cv_t = transcribe_const_t< Src, transcribe_volatile_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<true>* = nullptr +// vs: +// if_t<true> = 0 + +template <bool V> +using if_t = enable_if_t<V, int>; + +template <bool V> +using if_not_t = enable_if_t<false == V, int>; + + +// String handling + +class 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. +class string { +public: + explicit string() + : m_str(nullptr) + , m_const_str("") + , m_length(0) { } + + explicit string(char *str) + : m_str(str) + , m_const_str(str ? str : "") + , m_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 (m_str) { + ecs_os_free(m_str); + } + } + + string(string&& str) { + ecs_os_free(m_str); + m_str = str.m_str; + m_const_str = str.m_const_str; + m_length = str.m_length; + str.m_str = nullptr; + } + + operator const char*() const { + return m_const_str; + } + + string& operator=(string&& str) { + ecs_os_free(m_str); + m_str = str.m_str; + m_const_str = str.m_const_str; + m_length = str.m_length; + str.m_str = nullptr; + return *this; + } + + // Ban implicit copies/allocations + string& operator=(const string& str) = delete; + string(const string& str) = delete; + + bool operator==(const flecs::string& str) const { + if (str.m_const_str == m_const_str) { + return true; + } + + if (!m_const_str || !str.m_const_str) { + return false; + } + + if (str.m_length != m_length) { + return false; + } + + return ecs_os_strcmp(str, m_const_str) == 0; + } + + bool operator!=(const flecs::string& str) const { + return !(*this == str); + } + + bool operator==(const char *str) const { + if (m_const_str == str) { + return true; + } + + if (!m_const_str || !str) { + return false; + } + + return ecs_os_strcmp(str, m_const_str) == 0; + } + + bool operator!=(const char *str) const { + return !(*this == str); + } + + const char* c_str() const { + return m_const_str; + } + + std::size_t length() { + return static_cast<std::size_t>(m_length); + } + + std::size_t size() { + return length(); + } + + void clear() { + ecs_os_free(m_str); + m_str = nullptr; + m_const_str = nullptr; + } + +protected: + // Must be constructed through string_view. This allows for using the string + // class for both owned and non-owned strings, which can reduce allocations + // when code conditionally should store a literal or an owned string. + // Making this constructor private forces the code to explicitly create a + // string_view which emphasizes that the string won't be freed by the class. + string(const char *str) + : m_str(nullptr) + , m_const_str(str ? str : "") + , m_length(str ? ecs_os_strlen(str) : 0) { } + + char *m_str = nullptr; + const char *m_const_str; + ecs_size_t m_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. +class string_view : public string { +public: + explicit string_view(const char *str) + : string(str) { } +}; + +// Wrapper around ecs_strbuf_t that provides a simple stringstream like API. +class stringstream { +public: + explicit stringstream() + : m_buf({}) { } + + ~stringstream() { + ecs_strbuf_reset(&m_buf); + } + + stringstream(stringstream&& str) { + ecs_strbuf_reset(&m_buf); + m_buf = str.m_buf; + str.m_buf = {}; + } + + stringstream& operator=(stringstream&& str) { + ecs_strbuf_reset(&m_buf); + m_buf = str.m_buf; + str.m_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(&m_buf, str); + return *this; + } + + flecs::string str() { + return flecs::string(ecs_strbuf_get(&m_buf)); + } + +private: + ecs_strbuf_t m_buf; +}; + +// Array class. Simple std::array like utility that is mostly there to aid +// template code, where the expanded array size would be 0. +template <typename T> +class array_iterator +{ +public: + explicit array_iterator(T* value, int index) { + m_value = value; + m_index = index; + } + + bool operator!=(array_iterator const& other) const + { + return m_index != other.m_index; + } + + T & operator*() const + { + return m_value[m_index]; + } + + array_iterator& operator++() + { + ++m_index; + return *this; + } + +private: + T* m_value; + int m_index; +}; + +template <typename T, size_t Size, class Enable = void> +class array { }; + +template <typename T, size_t Size> +class array<T, Size, enable_if_t<Size != 0> > { +public: + array() {}; + + array(const T (&elems)[Size]) { + int i = 0; + for (auto it = this->begin(); it != this->end(); ++ it) { + *it = elems[i ++]; + } + } + + T& operator[](size_t index) { + return m_array[index]; + } + + array_iterator<T> begin() { + return array_iterator<T>(m_array, 0); + } + + array_iterator<T> end() { + return array_iterator<T>(m_array, Size); + } + + size_t size() { + return Size; + } + + T* ptr() { + return m_array; + } +private: + T m_array[Size]; +}; + +// Specialized class for zero-sized array +template <typename T, size_t Size> +class array<T, Size, enable_if_t<Size == 0>> { +public: + array() {}; + array(const T* (&elems)) { (void)elems; } + T operator[](size_t index) { abort(); (void)index; return T(); } + array_iterator<T> begin() { return array_iterator<T>(nullptr, 0); } + array_iterator<T> end() { return array_iterator<T>(nullptr, 0); } + + size_t size() { + return 0; + } + + T* ptr() { + return NULL; + } +}; + +namespace _ +{ + +// Utility to prevent static assert from immediately triggering +template <class... T> +struct always_false { + static const bool value = false; +}; + +} // namespace _ + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/cpp/world.hpp b/fggl/ecs2/flecs/include/flecs/cpp/world.hpp new file mode 100644 index 0000000000000000000000000000000000000000..56d6cad540dc010ec2ed731b11f28daf4c3854e9 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/cpp/world.hpp @@ -0,0 +1,939 @@ + +namespace flecs +{ + +/** Static helper functions to assign a component value */ + +// set(T&&), T = constructible +template <typename T, if_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, NULL)); + dst = std::move(value); + + ecs_modified_id(world, entity, id); +} + +// set(const T&), T = constructible +template <typename T, if_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, entity_t entity, const T& value, ecs_id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, NULL)); + dst = value; + + ecs_modified_id(world, entity, id); +} + +// set(T&&), T = not constructible +template <typename T, if_not_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + bool is_new = false; + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, &is_new)); + + /* If type is not constructible get_mut should assert on new values */ + ecs_assert(!is_new, ECS_INTERNAL_ERROR, NULL); + + dst = std::move(value); + + ecs_modified_id(world, entity, id); +} + +// set(const T&), T = not constructible +template <typename T, if_not_t< is_flecs_constructible<T>::value > = 0> +inline void set(world_t *world, id_t entity, const T& value, id_t id) { + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + + bool is_new = false; + T& dst = *static_cast<T*>(ecs_get_mut_id(world, entity, id, &is_new)); + + /* If type is not constructible get_mut should assert on new values */ + ecs_assert(!is_new, ECS_INTERNAL_ERROR, NULL); + dst = value; + + ecs_modified_id(world, entity, id); +} + +// emplace for T(Args...) +template <typename T, typename ... Args, if_t< + std::is_constructible<actual_type_t<T>, Args...>::value || + std::is_default_constructible<actual_type_t<T>>::value > = 0> +inline void emplace(world_t *world, id_t entity, Args&&... args) { + id_t id = _::cpp_type<T>::id(world); + + ecs_assert(_::cpp_type<T>::size() != 0, ECS_INVALID_PARAMETER, NULL); + T& dst = *static_cast<T*>(ecs_emplace_id(world, entity, id)); + + FLECS_PLACEMENT_NEW(&dst, T{std::forward<Args>(args)...}); + + ecs_modified_id(world, entity, id); +} + +// emplace for T(flecs::entity, Args...) +template <typename T, typename ... Args, if_t< + std::is_constructible<actual_type_t<T>, flecs::entity, Args...>::value > = 0> +inline void emplace(world_t *world, id_t entity, Args&&... args); + +// set(T&&) +template <typename T, typename A> +inline void set(world_t *world, entity_t entity, A&& value) { + id_t id = _::cpp_type<T>::id(world); + flecs::set(world, entity, std::forward<A&&>(value), id); +} + +// set(const T&) +template <typename T, typename A> +inline void set(world_t *world, entity_t entity, const A& value) { + id_t id = _::cpp_type<T>::id(world); + flecs::set(world, entity, value, id); +} + + +/** 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. + */ +class world final { +public: + /** Create world. + */ + explicit world() + : m_world( ecs_init() ) + , m_owned( true ) { 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[]) + : m_world( ecs_init_w_args(argc, argv) ) + , m_owned( true ) { init_builtin_components(); } + + /** Create world from C world. + */ + explicit world(world_t *w) + : m_world( w ) + , m_owned( false ) { } + + /** Not allowed to copy a world. May only take a reference. + */ + world(const world& obj) = delete; + + world(world&& obj) { + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + } + + /* Implicit conversion to world_t* */ + operator world_t*() const { return m_world; } + + /** Not allowed to copy a world. May only take a reference. + */ + world& operator=(const world& obj) = delete; + + world& operator=(world&& obj) { + this->~world(); + + m_world = obj.m_world; + m_owned = obj.m_owned; + obj.m_world = nullptr; + obj.m_owned = false; + return *this; + } + + ~world() { + if (m_owned && ecs_stage_is_async(m_world)) { + ecs_async_stage_free(m_world); + } else + if (m_owned && m_world) { + ecs_fini(m_world); + } + } + + /** Obtain pointer to C world object. + */ + world_t* c_ptr() const { + return m_world; + } + + /** Enable tracing. + * + * @param level The tracing level. + */ + static void enable_tracing(int level) { + ecs_tracing_enable(level); + } + + /** Enable tracing with colors. + * + * @param enabled Whether to enable tracing with colors. + */ + static void enable_tracing_color(bool enabled) { + ecs_tracing_color_enable(enabled); + } + + void set_pipeline(const flecs::pipeline& pip) const; + + /** Progress world, run all systems. + * + * @param delta_time Custom delta_time. If 0 is provided, Flecs will automatically measure delta_time. + */ + bool progress(FLECS_FLOAT delta_time = 0.0) const { + return ecs_progress(m_world, delta_time); + } + + /** Get last delta_time. + */ + FLECS_FLOAT delta_time() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->delta_time; + } + + /** Signal application should quit. + * After calling this operation, the next call to progress() returns false. + */ + void quit() { + ecs_quit(m_world); + } + + /** Test if quit() has been called. + */ + bool should_quit() { + return ecs_should_quit(m_world); + } + + /** Get id from a type. + */ + template <typename T> + flecs::id id() const; + + /** Id factory. + */ + template <typename ... Args> + flecs::id id(Args&&... args) const; + + /** Get pair id from relation, object + */ + template <typename R, typename O> + flecs::id pair() const; + + /** Get pair id from relation, object + */ + template <typename R> + flecs::id pair(entity_t o) const; + + /** Get pair id from relation, object + */ + flecs::id pair(entity_t r, entity_t o) const; + + /** 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. + */ + FLECS_FLOAT frame_begin(float delta_time = 0) { + return ecs_frame_begin(m_world, delta_time); + } + + /** End frame. + * This operation must be called at the end of the frame, and always after + * ecs_frame_begin. + * + * This function should only be ran from the main thread. + */ + void frame_end() { + ecs_frame_end(m_world); + } + + /** Begin staging. + * When an application does not use ecs_progress to control the main loop, it + * can still use Flecs features such as the defer queue. When an application + * needs to stage changes, it needs to call this function after ecs_frame_begin. + * A call to ecs_staging_begin must be followed by a call to ecs_staging_end. + * + * When staging is enabled, modifications to entities are stored to a stage. + * This ensures that arrays are not modified while iterating. Modifications are + * merged back to the "main stage" when ecs_staging_end is invoked. + * + * While the world is in staging mode, no structural changes (add/remove/...) + * can be made to the world itself. Operations must be executed on a stage + * instead (see ecs_get_stage). + * + * This function should only be ran from the main thread. + * + * @return Whether world is currently staged. + */ + bool staging_begin() { + return ecs_staging_begin(m_world); + } + + /** End staging. + * Leaves staging mode. After this operation the world may be directly mutated + * again. By default this operation also merges data back into the world, unless + * automerging was disabled explicitly. + * + * This function should only be ran from the main thread. + */ + void staging_end() { + ecs_staging_end(m_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. + */ + bool defer_begin() { + return ecs_defer_begin(m_world); + } + + /** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + */ + bool defer_end() { + return ecs_defer_end(m_world); + } + + /** Test whether deferring is enabled. + */ + bool is_deferred() { + return ecs_is_deferred(m_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_stages() operation is useful for applications that want to manage + * their own stages and/or threads. + * + * @param stages The number of stages. + */ + void set_stages(int32_t stages) const { + ecs_set_stages(m_world, stages); + } + + /** Get number of configured stages. + * Return number of stages set by set_stages. + * + * @return The number of stages used for threading. + */ + int32_t get_stage_count() const { + return ecs_get_stage_count(m_world); + } + + /** 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_get_stage_id(m_world); + } + + /** Enable/disable automerging for world or stage. + * When automerging is enabled, staged data will automatically be merged + * with the world when staging ends. This happens at the end of progress(), + * at a sync point or when staging_end() is called. + * + * Applications can exercise more control over when data from a stage is + * merged by disabling automerging. This requires an application to + * explicitly call merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this + * function is invoked on a stage, automerging is only set for that specific + * stage. + * + * @param automerge Whether to enable or disable automerging. + */ + void set_automerge(bool automerge) { + ecs_set_automerge(m_world, automerge); + } + + /** Merge world or stage. + * When automatic merging is disabled, an application can call this + * operation on either an individual stage, or on the world which will merge + * all stages. This operation may only be called when staging is not enabled + * (either after progress() or after staging_end()). + * + * This operation may be called on an already merged stage or world. + */ + void merge() { + ecs_merge(m_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(m_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. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * + * @return The stage. + */ + flecs::world async_stage() const { + auto result = flecs::world(ecs_async_stage_new(m_world)); + result.m_owned = true; + return result; + } + + /** 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( + m_world ? const_cast<flecs::world_t*>(ecs_get_world(m_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. + */ + bool is_readonly() const { + return ecs_stage_is_readonly(m_world); + } + + /** Set number of threads. + * This will distribute the load evenly across the configured number of + * threads for each system. + * + * @param threads Number of threads. + */ + void set_threads(int32_t threads) const { + ecs_set_threads(m_world, threads); + } + + /** Get number of threads. + * + * @return Number of configured threads. + */ + int32_t get_threads() const { + return ecs_get_threads(m_world); + } + + /** Get index of current thread. + * + * @return Unique index for current thread. + */ + ECS_DEPRECATED("use get_stage_id") + int32_t get_thread_index() const { + return ecs_get_stage_id(m_world); + } + + /** Set target FPS + * This will ensure that the main loop (world::progress) does not run faster + * than the specified frames per second. + * + * @param target_fps Target frames per second. + */ + void set_target_fps(FLECS_FLOAT target_fps) const { + ecs_set_target_fps(m_world, target_fps); + } + + /** Get target FPS + * + * @return Configured frames per second. + */ + FLECS_FLOAT get_target_fps() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->target_fps; + } + + /** Get tick + * + * @return Monotonically increasing frame count. + */ + int32_t get_tick() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->frame_count_total; + } + + /** Set timescale + * + * @return Monotonically increasing frame count. + */ + void set_time_scale(FLECS_FLOAT mul) const { + ecs_set_time_scale(m_world, mul); + } + + /** Get timescale + * + * @return Monotonically increasing frame count. + */ + FLECS_FLOAT get_time_scale() const { + const ecs_world_info_t *stats = ecs_get_world_info(m_world); + return stats->time_scale; + } + + /** Set world context. + * Set a context value that can be accessed by anyone that has a reference + * to the world. + * + * @param ctx The world context. + */ + void set_context(void* ctx) const { + ecs_set_context(m_world, ctx); + } + + /** Get world context. + * + * @return The configured world context. + */ + void* get_context() const { + return ecs_get_context(m_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. + */ + void dim(int32_t entity_count) const { + ecs_dim(m_world, entity_count); + } + + /** Preallocate memory for type + * This function preallocates memory for the component arrays of the + * specified type. + * + * @param type Type to preallocate memory for. + * @param entity_count Number of entities to preallocate memory for. + */ + void dim_type(type_t t, int32_t entity_count) const { + ecs_dim_type(m_world, t, 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. + */ + void set_entity_range(entity_t min, entity_t max) const { + ecs_set_entity_range(m_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. + */ + void enable_range_check(bool enabled) const { + ecs_enable_range_check(m_world, enabled); + } + + /** Disables inactive systems. + * + * This removes systems that are not matched with any entities from the main + * loop. Systems are only added to the main loop after they first match with + * entities, but are not removed automatically. + * + * This function allows an application to manually disable inactive systems + * which removes them from the main loop. Doing so will cause Flecs to + * rebuild the pipeline in the next iteration. + * + * @param level The tracing level. + */ + void deactivate_systems() { + ecs_deactivate_systems(m_world); + } + + /** Set current scope. + * + * @param scope The scope to set. + * @return The current scope; + */ + flecs::entity set_scope(const flecs::entity& scope) const; + + /** Get current scope. + * + * @return The current scope. + */ + flecs::entity get_scope() const; + + /** Lookup entity by name. + * + * @param name Entity name. + */ + flecs::entity lookup(const char *name) const; + + /** Set singleton component. + */ + template <typename T> + void set(const T& value) const { + flecs::set<T>(m_world, _::cpp_type<T>::id(m_world), value); + } + + template <typename T> + void set(T&& value) const { + flecs::set<T>(m_world, _::cpp_type<T>::id(m_world), + std::forward<T&&>(value)); + } + + template <typename T, typename ... Args> + void emplace(Args&&... args) const { + flecs::emplace<T>(m_world, _::cpp_type<T>::id(m_world), + std::forward<Args>(args)...); + } + + /** Get mut singleton component. + */ + template <typename T> + T* get_mut() const; + + /** Mark singleton component as modified. + */ + template <typename T> + void modified() const; + + /** Patch singleton component. + */ + template <typename T, typename Func> + void patch(const Func& func) const; + + /** Get singleton component. + */ + template <typename T> + const T* get() const; + + /** Test if world has singleton component. + */ + template <typename T> + bool has() const; + + /** Add singleton component. + */ + template <typename T> + void add() const; + + /** Remove singleton component. + */ + template <typename T> + void remove() const; + + /** Get id for type. + */ + template <typename T> + entity_t type_id() { + return _::cpp_type<T>::id(m_world); + } + + /** Get singleton entity for type. + */ + template <typename T> + flecs::entity singleton(); + + /** Create alias for component. + * + * @tparam Component to create an alias for. + * @param alias Alias for the component. + */ + template <typename T> + flecs::entity use(const char *alias = nullptr); + + /** 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); + + /** 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); + + /** Count entities matching a component. + * + * @tparam T The component to use for matching. + */ + template <typename T> + int count() const { + return ecs_count_id(m_world, _::cpp_type<T>::id(m_world)); + } + + flecs::filter_iterator begin() const; + flecs::filter_iterator end() const; + + /** Enable locking. + * + * @param enabled True if locking should be enabled, false if not. + */ + bool enable_locking(bool enabled) { + return ecs_enable_locking(m_world, enabled); + } + + /** Lock world. + */ + void lock() { + ecs_lock(m_world); + } + + /** Unlock world. + */ + void unlock() { + ecs_unlock(m_world); + } + + /** All entities created in function are created with id. + */ + template <typename Func> + void with(id_t with_id, const Func& func) const { + ecs_id_t prev = ecs_set_with(m_world, with_id); + func(); + ecs_set_with(m_world, prev); + } + + /** All entities created in function are created with type. + */ + template <typename T, typename Func> + void with(const Func& func) const { + with(this->id<T>(), func); + } + + /** All entities created in function are created with relation. + */ + template <typename Relation, typename Object, typename Func> + void with(const Func& func) const { + with(ecs_pair(this->id<Relation>(), this->id<Object>()), func); + } + + /** All entities created in function are created with relation. + */ + template <typename Relation, typename Func> + void with(id_t object, const Func& func) const { + with(ecs_pair(this->id<Relation>(), object), func); + } + + /** All entities created in function are created with relation. + */ + template <typename Func> + void with(id_t relation, id_t object, const Func& func) const { + with(ecs_pair(relation, object), func); + } + + /** All entities created in function are created in scope. All operations + * called in function (such as lookup) are relative to scope. + */ + template <typename Func> + void scope(id_t parent, const Func& func) const { + ecs_entity_t prev = ecs_set_scope(m_world, parent); + func(); + ecs_set_scope(m_world, prev); + } + + /** Defer all operations called in function. If the world is already in + * deferred mode, do nothing. + */ + template <typename Func> + void defer(const Func& func) const { + ecs_defer_begin(m_world); + func(); + ecs_defer_end(m_world); + } + + /** Iterate over all entities with provided component. + * The function parameter must match the following signature: + * void(*)(T&) or + * void(*)(flecs::entity, T&) + */ + template <typename T, typename Func> + void each(Func&& func) const; + + /** Iterate over all entities with provided (component) id. + */ + template <typename Func> + void each(flecs::id_t term_id, Func&& func) const; + + /** Iterate over all entities with components in argument list of function. + * The function parameter must match the following signature: + * void(*)(T&, U&, ...) or + * void(*)(flecs::entity, T&, U&, ...) + */ + template <typename Func> + void each(Func&& func) const; + + /** Create a prefab. + */ + template <typename... Args> + flecs::entity entity(Args &&... args) const; + + /** Create an entity. + */ + template <typename... Args> + flecs::entity prefab(Args &&... args) const; + + /** Create a type. + */ + template <typename... Args> + flecs::type type(Args &&... args) const; + + /** Create a pipeline. + */ + template <typename... Args> + flecs::pipeline pipeline(Args &&... args) const; + + /** Create a module. + */ + template <typename Module, typename... Args> + flecs::entity module(Args &&... args) const; + + /** Import a module. + */ + template <typename Module> + flecs::entity import(); // Cannot be const because modules accept a non-const world + + /** Create a system from an entity + */ + flecs::system<> system(flecs::entity e) const; + + /** Create a system. + */ + template <typename... Comps, typename... Args> + flecs::system_builder<Comps...> system(Args &&... args) const; + + /** Create an observer. + */ + template <typename... Comps, typename... Args> + flecs::observer_builder<Comps...> observer(Args &&... args) const; + + /** Create a filter. + */ + template <typename... Comps, typename... Args> + flecs::filter<Comps...> filter(Args &&... args) const; + + /** Create a filter builder. + */ + template <typename... Comps, typename... Args> + flecs::filter_builder<Comps...> filter_builder(Args &&... args) const; + + /** Create a query. + */ + template <typename... Comps, typename... Args> + flecs::query<Comps...> query(Args &&... args) const; + + /** Create a query builder. + */ + template <typename... Comps, typename... Args> + flecs::query_builder<Comps...> query_builder(Args &&... args) const; + + /** Create a term + */ + template<typename... Args> + flecs::term term(Args &&... args) const; + + /** Create a term for a type + */ + template<typename T, typename... Args> + flecs::term term(Args &&... args) const; + + /** Create a term for a pair + */ + template<typename R, typename O, typename... Args> + flecs::term term(Args &&... args) const; + + /** Register a component. + */ + template <typename T, typename... Args> + flecs::entity component(Args &&... args) const; + + /** Register a POD component. + */ + template <typename T, typename... Args> + flecs::entity pod_component(Args &&... args) const; + + /** Register a relocatable component. + */ + template <typename T, typename... Args> + flecs::entity relocatable_component(Args &&... args) const; + + /** Create a snapshot. + */ + template <typename... Args> + flecs::snapshot snapshot(Args &&... args) const; + +private: + void init_builtin_components(); + + world_t *m_world; + bool m_owned; +}; + +// Downcast utility to make world available to classes in inheritance hierarchy +template<typename Base> +class world_base { +public: + template<typename IBuilder> + static flecs::world world(const IBuilder *self) { + return flecs::world(static_cast<const Base*>(self)->m_world); + } + + flecs::world world() const { + return flecs::world(static_cast<const Base*>(this)->m_world); + } +}; + +} // namespace flecs diff --git a/fggl/ecs2/flecs/include/flecs/flecs.hpp b/fggl/ecs2/flecs/include/flecs/flecs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2e5e61e8874666a705085df2709e4656d381f59d --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/flecs.hpp @@ -0,0 +1,217 @@ +/** + * @file flecs.hpp + * @brief Flecs C++ API. + * + * This is a C++11 wrapper around the Flecs C API. + */ + +#pragma once + +// The C++ API does not use STL, save for type_traits +#include <type_traits> + +// 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 + +namespace flecs { + +//////////////////////////////////////////////////////////////////////////////// +//// Forward declarations and types +//////////////////////////////////////////////////////////////////////////////// + +using world_t = ecs_world_t; +using id_t = ecs_id_t; +using entity_t = ecs_entity_t; +using type_t = ecs_type_t; +using snapshot_t = ecs_snapshot_t; +using filter_t = ecs_filter_t; +using query_t = ecs_query_t; +using ref_t = ecs_ref_t; +using iter_t = ecs_iter_t; +using ComponentLifecycle = EcsComponentLifecycle; + +enum inout_kind_t { + InOutDefault = EcsInOutDefault, + 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 var_kind_t { + VarDefault = EcsVarDefault, + VarIsEntity = EcsVarIsEntity, + VarIsVariable = EcsVarIsVariable +}; + +class world; +class world_async_stage; +class snapshot; +class id; +class entity; +class entity_view; +class type; +class pipeline; +class iter; +class term; +class filter_iterator; +class child_iterator; +class world_filter; +class snapshot_filter; +class query_base; + +template<typename ... Components> +class filter; + +template<typename ... Components> +class query; + +template<typename ... Components> +class system; + +template<typename ... Components> +class observer; + +template <typename ... Components> +class filter_builder; + +template <typename ... Components> +class query_builder; + +template <typename ... Components> +class system_builder; + +template <typename ... Components> +class observer_builder; + +namespace _ +{ +template <typename T, typename U = int> +class cpp_type; + +template <typename Func, typename ... Components> +class each_invoker; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Builtin components and tags +//////////////////////////////////////////////////////////////////////////////// + +/* Builtin components */ +using Component = EcsComponent; +using Type = EcsType; +using Identifier = EcsIdentifier; +using Timer = EcsTimer; +using RateFilter = EcsRateFilter; +using TickSource = EcsTickSource; +using Query = EcsQuery; +using Trigger = EcsTrigger; +using Observer = EcsObserver; + +/* Builtin opaque components */ +static const flecs::entity_t System = ecs_id(EcsSystem); + +/* Builtin set constants */ +static const uint8_t DefaultSet = EcsDefaultSet; +static const uint8_t Self = EcsSelf; +static const uint8_t SuperSet = EcsSuperSet; +static const uint8_t SubSet = EcsSubSet; +static const uint8_t Cascade = EcsCascade; +static const uint8_t All = EcsAll; +static const uint8_t Nothing = EcsNothing; + +/* Builtin tag ids */ +static const flecs::entity_t Module = EcsModule; +static const flecs::entity_t Prefab = EcsPrefab; +static const flecs::entity_t Hidden = EcsHidden; +static const flecs::entity_t Disabled = EcsDisabled; +static const flecs::entity_t DisabledIntern = EcsDisabledIntern; +static const flecs::entity_t Inactive = EcsInactive; +static const flecs::entity_t OnDemand = EcsOnDemand; +static const flecs::entity_t Monitor = EcsMonitor; +static const flecs::entity_t Pipeline = EcsPipeline; + +/* Trigger tags */ +static const flecs::entity_t OnAdd = EcsOnAdd; +static const flecs::entity_t OnRemove = EcsOnRemove; +static const flecs::entity_t OnSet = EcsOnSet; +static const flecs::entity_t UnSet = EcsUnSet; + +/* Builtin pipeline tags */ +static const flecs::entity_t PreFrame = EcsPreFrame; +static const flecs::entity_t OnLoad = EcsOnLoad; +static const flecs::entity_t PostLoad = EcsPostLoad; +static const flecs::entity_t PreUpdate = EcsPreUpdate; +static const flecs::entity_t OnUpdate = EcsOnUpdate; +static const flecs::entity_t OnValidate = EcsOnValidate; +static const flecs::entity_t PostUpdate = EcsPostUpdate; +static const flecs::entity_t PreStore = EcsPreStore; +static const flecs::entity_t OnStore = EcsOnStore; +static const flecs::entity_t PostFrame = EcsPostFrame; + +/** Builtin roles */ +static const flecs::entity_t Pair = ECS_PAIR; +static const flecs::entity_t Switch = ECS_SWITCH; +static const flecs::entity_t Case = ECS_CASE; +static const flecs::entity_t Owned = ECS_OWNED; + +/* Builtin entity ids */ +static const flecs::entity_t Flecs = EcsFlecs; +static const flecs::entity_t FlecsCore = EcsFlecsCore; +static const flecs::entity_t World = EcsWorld; + +/* Ids used by rule solver */ +static const flecs::entity_t Wildcard = EcsWildcard; +static const flecs::entity_t This = EcsThis; +static const flecs::entity_t Transitive = EcsTransitive; +static const flecs::entity_t Final = EcsFinal; +static const flecs::entity_t Tag = EcsTag; + +/* Builtin relationships */ +static const flecs::entity_t IsA = EcsIsA; +static const flecs::entity_t ChildOf = EcsChildOf; + +/* Builtin identifiers */ +static const flecs::entity_t Name = EcsName; +static const flecs::entity_t Symbol = EcsSymbol; + +/* Cleanup rules */ +static const flecs::entity_t OnDelete = EcsOnDelete; +static const flecs::entity_t OnDeleteObject = EcsOnDeleteObject; +static const flecs::entity_t Remove = EcsRemove; +static const flecs::entity_t Delete = EcsDelete; +static const flecs::entity_t Throw = EcsThrow; + +} + +#include <flecs/cpp/util.hpp> +#include <flecs/cpp/pair.hpp> +#include <flecs/cpp/function_traits.hpp> +#include <flecs/cpp/lifecycle_traits.hpp> +#include <flecs/cpp/iter.hpp> +#include <flecs/cpp/world.hpp> +#include <flecs/cpp/id.hpp> +#include <flecs/cpp/entity.hpp> +#include <flecs/cpp/component.hpp> +#include <flecs/cpp/invoker.hpp> +#include <flecs/cpp/builder.hpp> +#include <flecs/cpp/type.hpp> +#include <flecs/cpp/module.hpp> +#include <flecs/cpp/filter.hpp> +#include <flecs/cpp/snapshot.hpp> +#include <flecs/cpp/filter_iterator.hpp> +#include <flecs/cpp/query.hpp> +#include <flecs/cpp/system.hpp> +#include <flecs/cpp/observer.hpp> +#include <flecs/cpp/impl.hpp> diff --git a/fggl/ecs2/flecs/include/flecs/modules/pipeline.h b/fggl/ecs2/flecs/include/flecs/modules/pipeline.h new file mode 100644 index 0000000000000000000000000000000000000000..2175598bee37e2ba92b0f09b0cb6712342af3423 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/modules/pipeline.h @@ -0,0 +1,180 @@ +/** + * @file 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 + +#ifndef FLECS_SYSTEM +#define FLECS_SYSTEM +#endif + +#include "system.h" + +#ifndef FLECS_PIPELINE_H +#define FLECS_PIPELINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef FLECS_LEGACY +#define ECS_PIPELINE(world, id, ...) \ + ecs_entity_t id = ecs_type_init(world, &(ecs_type_desc_t){\ + .entity = {\ + .name = #id,\ + .add = {EcsPipeline}\ + },\ + .ids_expr = #__VA_ARGS__\ + }); +#endif + +/** 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. + * @param pipeline The pipeline to set. + */ +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, + FLECS_FLOAT 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, + FLECS_FLOAT 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. + * + * Note: calling this function from an application currently only works in + * single threaded applications with a single stage. + * + * @param world The world. + * @param pipeline The pipeline to run. + */ +FLECS_API +void ecs_pipeline_run( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time); + +/** Deactivate systems that are not matched with tables. + * By default Flecs deactivates systems that are not matched with any tables. + * However, once a system has been matched with a table it remains activated, to + * prevent systems from continuously becoming active and inactive. + * + * To re-deactivate systems, an application can invoke this function, which will + * deactivate all systems that are not matched with any tables. + * + * @param world The world. + */ +FLECS_API +void ecs_deactivate_systems( + ecs_world_t *world); + + +//////////////////////////////////////////////////////////////////////////////// +//// 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. */ +FLECS_API +void ecs_set_threads( + ecs_world_t *world, + int32_t threads); + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Pipeline component is empty: components and tags in module are static */ +typedef struct FlecsPipeline { + int32_t dummy; +} FlecsPipeline; + +FLECS_API +void FlecsPipelineImport( + ecs_world_t *world); + +#define FlecsPipelineImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/modules/system.h b/fggl/ecs2/flecs/include/flecs/modules/system.h new file mode 100644 index 0000000000000000000000000000000000000000..dfc6bb5dbe9568f7f5aa131924f1c9977867be74 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/modules/system.h @@ -0,0 +1,305 @@ +/** + * @file 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 + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#include "../addons/module.h" + +#ifndef FLECS_SYSTEMS_H +#define FLECS_SYSTEMS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Components +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +extern ecs_type_t + ecs_type(EcsSystem), + ecs_type(EcsTickSource); + +/* Component used to provide a tick source to systems */ +typedef struct EcsTickSource { + bool tick; /* True if providing tick */ + FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ +} EcsTickSource; + +//////////////////////////////////////////////////////////////////////////////// +//// Systems API +//////////////////////////////////////////////////////////////////////////////// + +/** System status change callback */ +typedef enum ecs_system_status_t { + EcsSystemStatusNone = 0, + EcsSystemEnabled, + EcsSystemDisabled, + EcsSystemActivated, + EcsSystemDeactivated +} ecs_system_status_t; + +/** System status action. + * The status action is invoked whenever a system is enabled or disabled. Note + * that a system may be enabled but may not actually match any entities. In this + * case the system is enabled but not _active_. + * + * In addition to communicating the enabled / disabled status, the action also + * communicates changes in the activation status of the system. A system becomes + * active when it has one or more matching entities, and becomes inactive when + * it no longer matches any entities. + * + * A system switches between enabled and disabled when an application invokes the + * ecs_enable operation with a state different from the state of the system, for + * example the system is disabled, and ecs_enable is invoked with enabled: true. + * + * Additionally a system may switch between enabled and disabled when it is an + * EcsOnDemand system, and interest is generated or lost for one of its [out] + * columns. + * + * @param world The world. + * @param system The system for which to set the action. + * @param action The action. + * @param ctx Context that will be passed to the action when invoked. + */ +typedef void (*ecs_system_status_action_t)( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_t status, + void *ctx); + +/* Use with ecs_system_init */ +typedef struct ecs_system_desc_t { + /* System entity creation parameters */ + ecs_entity_desc_t entity; + + /* System query parameters */ + ecs_query_desc_t query; + + /* System callback, invoked when system is ran */ + ecs_iter_action_t callback; + + /* System status callback, invoked when system status changes */ + ecs_system_status_action_t status_callback; + + /* Associate with entity */ + ecs_entity_t self; + + /* Context to be passed to callback (as ecs_iter_t::param) */ + void *ctx; + + /* Context to be passed to system status callback */ + void *status_ctx; + + /* Binding context, for when system is implemented in other language */ + void *binding_ctx; + + /* Functions that are invoked during system cleanup to free context data. + * When set, functions are called unconditionally, even when the ctx + * pointers are NULL. */ + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t status_ctx_free; + ecs_ctx_free_t binding_ctx_free; + + /* Interval in seconds at which the system should run */ + FLECS_FLOAT interval; + + /* Rate at which the system should run */ + int32_t rate; + + /* External tick soutce that determines when system ticks */ + ecs_entity_t tick_source; +} 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); + +#ifndef FLECS_LEGACY +#define ECS_SYSTEM(world, id, kind, ...) \ + ecs_iter_action_t ecs_iter_action(id) = id;\ + ecs_entity_t id = ecs_system_init(world, &(ecs_system_desc_t){\ + .entity = { .name = #id, .add = {kind} },\ + .query.filter.expr = #__VA_ARGS__,\ + .callback = ecs_iter_action(id)\ + });\ + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);\ + (void)ecs_iter_action(id);\ + (void)id; +#endif + +/* Deprecated, use ecs_trigger_init */ +#define ECS_TRIGGER(world, trigger_name, kind, component) \ + ecs_entity_t __F##trigger_name = ecs_trigger_init(world, &(ecs_trigger_desc_t){\ + .entity.name = #trigger_name,\ + .callback = trigger_name,\ + .expr = #component,\ + .events = {kind},\ + });\ + ecs_entity_t trigger_name = __F##trigger_name;\ + ecs_assert(trigger_name != 0, ECS_INVALID_PARAMETER, NULL);\ + (void)__F##trigger_name;\ + (void)trigger_name; + +/** 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 prematched 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 + * cominbation 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, + FLECS_FLOAT 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, + FLECS_FLOAT delta_time, + void *param); + +/** Run system with offset/limit and type filter. + * This operation is the same as ecs_run, but filters the entities that will be + * iterated by the system. + * + * Entities can be filtered in two ways. Offset and limit control the range of + * entities that is iterated over. The range is applied to all entities matched + * with the system, thus may cover multiple archetypes. + * + * The type filter controls which entity types the system will evaluate. Only + * types that contain all components in the type filter will be iterated over. A + * type filter is only evaluated once per table, which makes filtering cheap if + * the number of entities is large and the number of tables is small, but not as + * cheap as filtering in the system signature. + * + * @param world The world. + * @param system The system to invoke. + * @param delta_time: The time passed since the last system invocation. + * @param filter A component or type to filter matched entities. + * @param param A user-defined parameter to pass to the system. + * @return handle to last evaluated entity if system was interrupted. + */ +FLECS_API +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param); + +/** Get the query object for a system. + * Systems use queries under the hood. This enables an application to get access + * to the underlying query object of a system. This can be useful when, for + * example, an application needs to enable sorting for a system. + * + * @param world The world. + * @param system The system from which to obtain the query. + * @return The query. + */ +FLECS_API +ecs_query_t* ecs_get_system_query( + const ecs_world_t *world, + ecs_entity_t system); + +/** Get system context. + * This operation returns the context pointer set for the system. If + * the provided entity is not a system, the function will return NULL. + * + * @param world The world. + * @param system The system from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_system_ctx( + const ecs_world_t *world, + ecs_entity_t system); + +/** Get system binding context. + * The binding context is a context typically used to attach any language + * binding specific data that is needed when invoking a callback that is + * implemented in another language. + * + * @param world The world. + * @param system The system from which to obtain the context. + * @return The context. + */ +FLECS_API +void* ecs_get_system_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system); + + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/* Pipeline component is empty: components and tags in module are static */ +typedef struct FlecsSystem { + int32_t dummy; +} FlecsSystem; + +FLECS_API +void FlecsSystemImport( + ecs_world_t *world); + +#define FlecsSystemImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/modules/timer.h b/fggl/ecs2/flecs/include/flecs/modules/timer.h new file mode 100644 index 0000000000000000000000000000000000000000..075044aedcdf525f2635115c5420dab1bd7d3db0 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/modules/timer.h @@ -0,0 +1,257 @@ +/** + * @file timer.h + * @brief Timer module. + * + * Timers can be used to trigger actions at periodic or one-shot intervals. They + * are typically used together with systems and pipelines. + */ + +#ifdef FLECS_TIMER + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifndef FLECS_TIMER_H +#define FLECS_TIMER_H + +#include "pipeline.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Components +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +extern ecs_type_t + ecs_type(EcsTimer), + ecs_type(EcsRateFilter); + +/** Component used for one shot/interval timer functionality */ +typedef struct EcsTimer { + FLECS_FLOAT timeout; /* Timer timeout period */ + FLECS_FLOAT time; /* Incrementing time value */ + int32_t fired_count; /* Number of times ticked */ + bool active; /* Is the timer active or not */ + bool single_shot; /* Is this a single shot timer */ +} EcsTimer; + +/* Apply a rate filter to a tick source */ +typedef struct EcsRateFilter { + ecs_entity_t src; /* Source of the rate filter */ + int32_t rate; /* Rate of the rate filter */ + int32_t tick_count; /* Number of times the rate filter ticked */ + FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ +} EcsRateFilter; + + +//////////////////////////////////////////////////////////////////////////////// +//// Timer API +//////////////////////////////////////////////////////////////////////////////// + +/** Set timer timeout. + * This operation executes any systems associated with the timer after the + * specified timeout value. If the entity contains an existing timer, the + * timeout value will be reset. The timer can be started and stopped with + * ecs_start_timer and ecs_stop_timer. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be be a tick source after this operation. Tick + * 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, + FLECS_FLOAT 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 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 +FLECS_FLOAT ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t tick_source); + +/** Set timer interval. + * This operation will continously invoke systems associated with the timer + * after the interval period expires. If the entity contains an existing timer, + * the interval value will be reset. + * + * The timer is synchronous, and is incremented each frame by delta_time. + * + * The tick_source entity will be be a tick source after this operation. Tick + * 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, + FLECS_FLOAT 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 +FLECS_FLOAT ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t tick_source); + +/** Start timer. + * This operation resets the timer and starts it with the specified timeout. The + * entity must have the EcsTimer component (added by ecs_set_timeout and + * ecs_set_interval). If the entity does not have the EcsTimer component this + * operation will assert. + * + * @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. The entity must have the + * EcsTimer component or this operation will assert. + * + * @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); + +/** 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 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 timer The timer 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 +//////////////////////////////////////////////////////////////////////////////// + +/* Timers module component */ +typedef struct FlecsTimer { + int32_t dummy; +} FlecsTimer; + +FLECS_API +void FlecsTimerImport( + ecs_world_t *world); + +#define FlecsTimerImportHandles(handles) + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/os_api.h b/fggl/ecs2/flecs/include/flecs/os_api.h new file mode 100644 index 0000000000000000000000000000000000000000..16c98e842216efb272809075c14f53c50d203a05 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/os_api.h @@ -0,0 +1,467 @@ +/** + * @file os_api.h + * @brief Operationg system abstractions. + * + * 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 + +#include <stdarg.h> +#include <errno.h> + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include <malloc.h> +#elif defined(__FreeBSD__) +#include <stdlib.h> +#else +#include <alloca.h> +#endif + +#if defined(_WIN32) +#define ECS_OS_WINDOWS +#elif defined(__linux__) +#define ECS_OS_LINUX +#elif defined(__APPLE__) && defined(__MACH__) +#define ECS_OS_DARWIN +#else +/* Unknown OS */ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_time_t { + uint32_t sec; + uint32_t nanosec; +} ecs_time_t; + +/* Allocation counters (not thread safe) */ +extern int64_t ecs_os_api_malloc_count; +extern int64_t ecs_os_api_realloc_count; +extern int64_t ecs_os_api_calloc_count; +extern int64_t ecs_os_api_free_count; + +/* Use handle types that _at least_ can store pointers */ +typedef uintptr_t ecs_os_thread_t; +typedef uintptr_t ecs_os_cond_t; +typedef uintptr_t ecs_os_mutex_t; +typedef uintptr_t ecs_os_dl_t; + +/* 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); + +/* Memory management */ +typedef +void* (*ecs_os_api_malloc_t)( + ecs_size_t size); + +typedef +void (*ecs_os_api_free_t)( + void *ptr); + +typedef +void* (*ecs_os_api_realloc_t)( + void *ptr, + ecs_size_t size); + +typedef +void* (*ecs_os_api_calloc_t)( + ecs_size_t size); + +typedef +char* (*ecs_os_api_strdup_t)( + const char *str); + +/* Threads */ +typedef +void* (*ecs_os_thread_callback_t)( + void*); + +typedef +ecs_os_thread_t (*ecs_os_api_thread_new_t)( + ecs_os_thread_callback_t callback, + void *param); + +typedef +void* (*ecs_os_api_thread_join_t)( + ecs_os_thread_t thread); + + +/* Atomic increment / decrement */ +typedef +int (*ecs_os_api_ainc_t)( + int32_t *value); + + +/* Mutex */ +typedef +ecs_os_mutex_t (*ecs_os_api_mutex_new_t)( + void); + +typedef +void (*ecs_os_api_mutex_lock_t)( + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_mutex_unlock_t)( + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_mutex_free_t)( + ecs_os_mutex_t mutex); + +/* Condition variable */ +typedef +ecs_os_cond_t (*ecs_os_api_cond_new_t)( + void); + +typedef +void (*ecs_os_api_cond_free_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_signal_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_broadcast_t)( + ecs_os_cond_t cond); + +typedef +void (*ecs_os_api_cond_wait_t)( + ecs_os_cond_t cond, + ecs_os_mutex_t mutex); + +typedef +void (*ecs_os_api_sleep_t)( + int32_t sec, + int32_t nanosec); + +typedef +void (*ecs_os_api_get_time_t)( + ecs_time_t *time_out); + +/* Logging */ +typedef +void (*ecs_os_api_log_t)( + const char *fmt, + va_list args); + +/* Application termination */ +typedef +void (*ecs_os_api_abort_t)( + void); + +/* Dynamic libraries */ +typedef +ecs_os_dl_t (*ecs_os_api_dlopen_t)( + const char *libname); + +typedef +ecs_os_proc_t (*ecs_os_api_dlproc_t)( + ecs_os_dl_t lib, + const char *procname); + +typedef +void (*ecs_os_api_dlclose_t)( + ecs_os_dl_t lib); + +typedef +char* (*ecs_os_api_module_to_path_t)( + const char *module_id); + +/* Prefix members of struct with 'ecs_' as some system headers may define + * macro's for functions like "strdup", "log" or "_free" */ + +typedef struct ecs_os_api_t { + /* API init / deinit */ + ecs_os_api_init_t init_; + ecs_os_api_fini_t fini_; + + /* Memory management */ + ecs_os_api_malloc_t malloc_; + ecs_os_api_realloc_t realloc_; + ecs_os_api_calloc_t calloc_; + ecs_os_api_free_t free_; + + /* Strings */ + ecs_os_api_strdup_t strdup_; + + /* Threads */ + ecs_os_api_thread_new_t thread_new_; + ecs_os_api_thread_join_t thread_join_; + + /* Atomic incremenet / decrement */ + ecs_os_api_ainc_t ainc_; + ecs_os_api_ainc_t adec_; + + /* Mutex */ + ecs_os_api_mutex_new_t mutex_new_; + ecs_os_api_mutex_free_t mutex_free_; + ecs_os_api_mutex_lock_t mutex_lock_; + ecs_os_api_mutex_lock_t mutex_unlock_; + + /* Condition variable */ + ecs_os_api_cond_new_t cond_new_; + ecs_os_api_cond_free_t cond_free_; + ecs_os_api_cond_signal_t cond_signal_; + ecs_os_api_cond_broadcast_t cond_broadcast_; + ecs_os_api_cond_wait_t cond_wait_; + + /* Time */ + ecs_os_api_sleep_t sleep_; + ecs_os_api_get_time_t get_time_; + + /* Logging */ + ecs_os_api_log_t log_; + ecs_os_api_log_t log_error_; + ecs_os_api_log_t log_debug_; + ecs_os_api_log_t log_warning_; + + /* Application termination */ + ecs_os_api_abort_t abort_; + + /* Dynamic library loading */ + ecs_os_api_dlopen_t dlopen_; + ecs_os_api_dlproc_t dlproc_; + ecs_os_api_dlclose_t dlclose_; + + /* Overridable function that translates from a logical module id to a + * shared library filename */ + ecs_os_api_module_to_path_t module_to_dl_; + + /* Overridable function that translates from a logical module id to a + * path that contains module-specif resources or assets */ + ecs_os_api_module_to_path_t module_to_etc_; +} ecs_os_api_t; + +FLECS_API +extern ecs_os_api_t ecs_os_api; + +FLECS_API +void ecs_os_init(void); + +FLECS_API +void ecs_os_fini(void); + +FLECS_API +void ecs_os_set_api( + ecs_os_api_t *os_api); + +FLECS_API +void ecs_os_set_api_defaults(void); + +/* 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(_MSC_VER) || defined(__MINGW32__) +#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 + +#define ecs_os_strset(dst, src) ecs_os_free(*dst); *dst = ecs_os_strdup(src) + +#ifdef __cplusplus +#define ecs_os_strlen(str) static_cast<ecs_size_t>(strlen(str)) +#define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, static_cast<size_t>(num)) +#define ecs_os_memcmp(ptr1, ptr2, num) memcmp(ptr1, ptr2, static_cast<size_t>(num)) +#define ecs_os_memcpy(ptr1, ptr2, num) memcpy(ptr1, ptr2, static_cast<size_t>(num)) +#define ecs_os_memset(ptr, value, num) memset(ptr, value, static_cast<size_t>(num)) +#define ecs_os_memmove(ptr, value, num) memmove(ptr, value, static_cast<size_t>(num)) +#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(ptr, value, num) memmove(ptr, value, (size_t)(num)) +#endif + +#define ecs_os_memcpy_t(ptr1, ptr2, T) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T)) +#define ecs_os_memcpy_n(ptr1, ptr2, T, count) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T) * count) + +#define ecs_os_strcmp(str1, str2) strcmp(str1, str2) +#define ecs_os_memset_t(ptr, value, T) ecs_os_memset(ptr, value, ECS_SIZEOF(T)) +#define ecs_os_memset_n(ptr, value, T, count) ecs_os_memset(ptr, value, ECS_SIZEOF(T) * count) + +#define ecs_os_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) + +#if defined(_MSC_VER) +#define ecs_os_strcat(str1, str2) strcat_s(str1, INT_MAX, str2) +#define ecs_os_sprintf(ptr, ...) sprintf_s(ptr, INT_MAX, __VA_ARGS__) +#define ecs_os_vsprintf(ptr, fmt, args) vsprintf_s(ptr, INT_MAX, fmt, args) +#define ecs_os_strcpy(str1, str2) strcpy_s(str1, INT_MAX, str2) +#ifdef __cplusplus +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, static_cast<size_t>(num)) +#else +#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, (size_t)(num)) +#endif +#else +#define ecs_os_strcat(str1, str2) strcat(str1, str2) +#define ecs_os_sprintf(ptr, ...) sprintf(ptr, __VA_ARGS__) +#define ecs_os_vsprintf(ptr, fmt, args) vsprintf(ptr, fmt, args) +#define ecs_os_strcpy(str1, str2) strcpy(str1, str2) +#ifdef __cplusplus +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, static_cast<size_t>(num)) +#else +#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, (size_t)(num)) +#endif +#endif + +/* Files */ +#if defined(_MSC_VER) +#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) + +/* Atomic increment / decrement */ +#define ecs_os_ainc(value) ecs_os_api.ainc_(value) +#define ecs_os_adec(value) ecs_os_api.adec_(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_get_time(time_out) ecs_os_api.get_time_(time_out) + +/* Logging (use functions to avoid using variadic macro arguments) */ +FLECS_API +void ecs_os_log(const char *fmt, ...); + +FLECS_API +void ecs_os_warn(const char *fmt, ...); + +FLECS_API +void ecs_os_err(const char *fmt, ...); + +FLECS_API +void ecs_os_dbg(const char *fmt, ...); + +FLECS_API +const char* ecs_os_strerror(int err); + +/* 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) + +/* Sleep with floating point time */ +FLECS_API +void ecs_sleepf( + double t); + +/* Measure time since provided timestamp */ +FLECS_API +double ecs_time_measure( + ecs_time_t *start); + +/* Calculate difference between two timestamps */ +FLECS_API +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2); + +/* Convert time value to a double */ +FLECS_API +double ecs_time_to_double( + ecs_time_t t); + +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 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 diff --git a/fggl/ecs2/flecs/include/flecs/private/api_defines.h b/fggl/ecs2/flecs/include/flecs/private/api_defines.h new file mode 100644 index 0000000000000000000000000000000000000000..d7bd0ca3eb126763daee36df8f51468627fff1ba --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/api_defines.h @@ -0,0 +1,392 @@ +/** + * @file api_defines.h + * @brief Supporting defines for the public API. + * + * This file contains constants / macro's 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 + +/* Standard library dependencies */ +#include <time.h> +#include <stdlib.h> +#include <assert.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> + +/* Non-standard but required. If not provided by platform, add manually. */ +#include <stdint.h> + +/* Contains macro's for importing / exporting symbols */ +#include "../bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __BAKE_LEGACY__ +#define FLECS_LEGACY +#endif + +/* Some symbols are only exported when building in debug build, to enable + * whitebox testing of internal datastructures */ +#ifndef NDEBUG +#define FLECS_DBG_API FLECS_API +#else +#define FLECS_DBG_API +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Language support defines +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY +#include <stdbool.h> +#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 + +typedef uint32_t ecs_flags32_t; +typedef uint64_t ecs_flags64_t; + +/* Keep unsigned integers out of the codebase as they do more harm than good */ +typedef int32_t ecs_size_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<int64_t>(alignof(T)) +#elif defined(_MSC_VER) +#define ECS_ALIGNOF(T) (int64_t)__alignof(T) +#elif defined(__GNUC__) +#define ECS_ALIGNOF(T) (int64_t)__alignof__(T) +#else +#define ECS_ALIGNOF(T) ((int64_t)&((struct { char c; T d; } *)0)->d) +#endif + +#if defined(__GNUC__) +#define ECS_UNUSED __attribute__((unused)) +#else +#define ECS_UNUSED +#endif + +#ifndef FLECS_NO_DEPRECATED_WARNINGS +#if defined(__GNUC__) +#define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +#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) + +/* 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<T>(V)) +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Reserved component ids +//////////////////////////////////////////////////////////////////////////////// + +/** Builtin component ids */ +#define FLECS__EEcsComponent (1) +#define FLECS__EEcsComponentLifecycle (2) +#define FLECS__EEcsType (3) +#define FLECS__EEcsIdentifier (4) +#define FLECS__EEcsTrigger (6) +#define FLECS__EEcsQuery (7) +#define FLECS__EEcsObserver (8) +// #define FLECS__EEcsIterable (9) + +/* System module component ids */ +#define FLECS__EEcsSystem (10) +#define FLECS__EEcsTickSource (11) + +/** Pipeline module component ids */ +#define FLECS__EEcsPipelineQuery (12) + +/** Timer module component ids */ +#define FLECS__EEcsTimer (13) +#define FLECS__EEcsRateFilter (14) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity id macro's +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_ROLE_MASK (0xFFull << 56) +#define ECS_ENTITY_MASK (0xFFFFFFFFull) +#define ECS_GENERATION_MASK (0xFFFFull << 32) +#define ECS_GENERATION(e) ((e & ECS_GENERATION_MASK) >> 32) +#define ECS_GENERATION_INC(e) ((e & ~ECS_GENERATION_MASK) | ((0xFFFF & (ECS_GENERATION(e) + 1)) << 32)) +#define ECS_COMPONENT_MASK (~ECS_ROLE_MASK) +#define ECS_HAS_ROLE(e, role) ((e & ECS_ROLE_MASK) == ECS_##role) +#define ECS_PAIR_RELATION(e) (ecs_entity_t_hi(e & ECS_COMPONENT_MASK)) +#define ECS_PAIR_OBJECT(e) (ecs_entity_t_lo(e)) +#define ECS_HAS_RELATION(e, rel) (ECS_HAS_ROLE(e, PAIR) && (ECS_PAIR_RELATION(e) == rel)) + +#define ECS_HAS_PAIR_OBJECT(e, rel, obj)\ + (ECS_HAS_RELATION(e, rel) && ECS_PAIR_OBJECT(e) == obj) + +#define ECS_HAS(id, has_id)(\ + (id == has_id) ||\ + (ECS_HAS_PAIR_OBJECT(id, ECS_PAIR_RELATION(has_id), ECS_PAIR_OBJECT(has_id)))) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convert between C typenames and variables +//////////////////////////////////////////////////////////////////////////////// + +/** Translate C type to ecs_type_t variable. */ +#define ecs_type(T) FLECS__T##T + +/** Translate C type to id. */ +#define ecs_id(T) FLECS__E##T + +/** Translate C type to module struct. */ +#define ecs_module(T) FLECS__M##T + +/** Translate C type to module struct. */ +#define ecs_module_ptr(T) FLECS__M##T##_ptr + +/** Translate C type to module struct. */ +#define ecs_iter_action(T) FLECS__F##T + + +//////////////////////////////////////////////////////////////////////////////// +//// 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)) + +/* Get object from pair with the correct (current) generation count */ +#define ecs_pair_relation(world, pair) ecs_get_alive(world, ECS_PAIR_RELATION(pair)) +#define ecs_pair_object(world, pair) ecs_get_alive(world, ECS_PAIR_OBJECT(pair)) + + +//////////////////////////////////////////////////////////////////////////////// +//// Convenience macro's for ctor, dtor, move and copy +//////////////////////////////////////////////////////////////////////////////// + +#ifndef FLECS_LEGACY + +/* Constructor / destructor convenience macro */ +#define ECS_XTOR_IMPL(type, postfix, var, ...)\ + void type##_##postfix(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *entity_ptr,\ + void *_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)entity_ptr;\ + (void)_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t entity = entity_ptr[i];\ + type *var = &((type*)_ptr)[i];\ + (void)entity;\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +/* Copy convenience macro */ +#define ECS_COPY_IMPL(type, dst_var, src_var, ...)\ + void type##_##copy(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *dst_entities,\ + const ecs_entity_t *src_entities,\ + void *_dst_ptr,\ + const void *_src_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)dst_entities;\ + (void)src_entities;\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t dst_entity = dst_entities[i];\ + ecs_entity_t src_entity = src_entities[i];\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_entity;\ + (void)src_entity;\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +/* Move convenience macro */ +#define ECS_MOVE_IMPL(type, dst_var, src_var, ...)\ + void type##_##move(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *dst_entities,\ + const ecs_entity_t *src_entities,\ + void *_dst_ptr,\ + void *_src_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)dst_entities;\ + (void)src_entities;\ + (void)_dst_ptr;\ + (void)_src_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t dst_entity = dst_entities[i];\ + ecs_entity_t src_entity = src_entities[i];\ + type *dst_var = &((type*)_dst_ptr)[i];\ + type *src_var = &((type*)_src_ptr)[i];\ + (void)dst_entity;\ + (void)src_entity;\ + (void)dst_var;\ + (void)src_var;\ + __VA_ARGS__\ + }\ + } + +/* Constructor / destructor convenience macro */ +#define ECS_ON_SET_IMPL(type, var, ...)\ + void type##_##on_set(\ + ecs_world_t *world,\ + ecs_entity_t component,\ + const ecs_entity_t *entity_ptr,\ + void *_ptr,\ + size_t _size,\ + int32_t _count,\ + void *ctx)\ + {\ + (void)world;\ + (void)component;\ + (void)entity_ptr;\ + (void)_ptr;\ + (void)_size;\ + (void)_count;\ + (void)ctx;\ + for (int32_t i = 0; i < _count; i ++) {\ + ecs_entity_t entity = entity_ptr[i];\ + type *var = &((type*)_ptr)[i];\ + (void)entity;\ + (void)var;\ + __VA_ARGS__\ + }\ + } + +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Error codes +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_INVALID_OPERATION (1) +#define ECS_INVALID_PARAMETER (2) +#define ECS_INVALID_DELETE (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_THREAD_ERROR (10) +#define ECS_CYCLE_DETECTED (11) + +#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_COLUMN_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_HAS_NO_DATA (44) +#define ECS_COLUMN_TYPE_MISMATCH (45) +#define ECS_NO_OUT_COLUMNS (46) + +#define ECS_TYPE_NOT_AN_ENTITY (60) +#define ECS_TYPE_CONSTRAINT_VIOLATION (61) +#define ECS_TYPE_INVALID_CASE (62) + +#define ECS_INVALID_WHILE_ITERATING (70) +#define ECS_LOCKED_STORAGE (71) +#define ECS_INVALID_FROM_WORKER (72) + +#define ECS_DESERIALIZE_FORMAT_ERROR (80) + + +//////////////////////////////////////////////////////////////////////////////// +//// Deprecated constants +//////////////////////////////////////////////////////////////////////////////// + +/* These constants should no longer be used, but are required by the core to + * guarantee backwards compatibility */ +#define ECS_AND (ECS_ROLE | (0x79ull << 56)) +#define ECS_OR (ECS_ROLE | (0x78ull << 56)) +#define ECS_XOR (ECS_ROLE | (0x77ull << 56)) +#define ECS_NOT (ECS_ROLE | (0x76ull << 56)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/api_support.h b/fggl/ecs2/flecs/include/flecs/private/api_support.h new file mode 100644 index 0000000000000000000000000000000000000000..5b9ab227f4646562036380e235500ec65b967dee --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/api_support.h @@ -0,0 +1,86 @@ +/** + * @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 + +#include "api_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** This reserves entity ids for components. Regular entity ids will start after + * this constant. This affects performance of table traversal, as edges with ids + * lower than this constant are looked up in an array, whereas constants higher + * than this id are looked up in a map. Increasing this value can improve + * performance at the cost of (significantly) higher memory usage. */ +#define ECS_HI_COMPONENT_ID (256) /* Maximum number of components */ + +/** The maximum number of nested function calls before the core will throw a + * cycle detected error */ +#define ECS_MAX_RECURSION (512) + +//////////////////////////////////////////////////////////////////////////////// +//// Global type handles +//////////////////////////////////////////////////////////////////////////////// + +/** Type handles to builtin components */ +FLECS_API +extern ecs_type_t + ecs_type(EcsComponent), + ecs_type(EcsComponentLifecycle), + ecs_type(EcsType), + ecs_type(EcsIdentifier); + +/** This allows passing 0 as type to functions that accept types */ +#define FLECS__TNULL 0 +#define FLECS__T0 0 +#define FLECS__E0 0 + +//////////////////////////////////////////////////////////////////////////////// +//// Functions used in declarative (macro) API +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +char* ecs_module_path_from_c( + const char *c_name); + +FLECS_API +bool ecs_component_has_actions( + const ecs_world_t *world, + ecs_entity_t component); + +FLECS_API +void ecs_add_module_tag( + ecs_world_t *world, + ecs_entity_t module); + +//////////////////////////////////////////////////////////////////////////////// +//// Signature API +//////////////////////////////////////////////////////////////////////////////// + +bool ecs_identifier_is_0( + const char *id); + +bool ecs_identifier_is_var( + const char *id); + +/** Calculate offset from address */ +#ifdef __cplusplus +#define ECS_OFFSET(o, offset) reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(o)) + (static_cast<uintptr_t>(offset))) +#else +#define ECS_OFFSET(o, offset) (void*)(((uintptr_t)(o)) + ((uintptr_t)(offset))) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/api_types.h b/fggl/ecs2/flecs/include/flecs/private/api_types.h new file mode 100644 index 0000000000000000000000000000000000000000..bce9c5a09e77a27753416f718c25f93de405b667 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/api_types.h @@ -0,0 +1,340 @@ +/** + * @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 + +#include "api_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Opaque types +//////////////////////////////////////////////////////////////////////////////// + +/** A stage enables modification while iterating and from multiple threads */ +typedef struct ecs_stage_t ecs_stage_t; + +/** A table is where entities and components are stored */ +typedef struct ecs_table_t ecs_table_t; + +/** A record stores data to map an entity id to a location in a table */ +typedef struct ecs_record_t ecs_record_t; + +/** Table column */ +typedef struct ecs_column_t ecs_column_t; + +/** Table data */ +typedef struct ecs_data_t ecs_data_t; + +/* Sparse set */ +typedef struct ecs_sparse_t ecs_sparse_t; + +/* Switch list */ +typedef struct ecs_switch_t ecs_switch_t; + +/* Internal structure to lookup tables for a (component) id */ +typedef struct ecs_id_record_t ecs_id_record_t; + +//////////////////////////////////////////////////////////////////////////////// +//// Non-opaque types +//////////////////////////////////////////////////////////////////////////////// + +struct ecs_record_t { + ecs_table_t *table; /* Identifies a type (and table) in world */ + int32_t row; /* Table row of the entity */ +}; + +/** Cached reference. */ +struct ecs_ref_t { + ecs_entity_t entity; /**< Entity of the reference */ + ecs_entity_t component; /**< Component of the reference */ + void *table; /**< Last known table */ + int32_t row; /**< Last known location in table */ + int32_t alloc_count; /**< Last known alloc count of table */ + ecs_record_t *record; /**< Pointer to record, if in main stage */ + const void *ptr; /**< Cached ptr */ +}; + +/** Array of entity ids that, other than a type, can live on the stack */ +typedef struct ecs_ids_t { + ecs_entity_t *array; /**< An array with entity ids */ + int32_t count; /**< The number of entities in the array */ +} ecs_ids_t; + +typedef struct ecs_page_cursor_t { + int32_t first; + int32_t count; +} ecs_page_cursor_t; + +typedef struct ecs_page_iter_t { + int32_t offset; + int32_t limit; + int32_t remaining; +} ecs_page_iter_t; + +/** Table specific data for iterators */ +typedef struct ecs_iter_table_t { + int32_t *columns; /**< Mapping from query terms to table columns */ + ecs_table_t *table; /**< The current table. */ + ecs_data_t *data; /**< Table component data */ + ecs_entity_t *components; /**< Components in current table */ + ecs_type_t *types; /**< Components in current table */ + ecs_ref_t *references; /**< References to entities (from query) */ +} ecs_iter_table_t; + +/** Scope-iterator specific data */ +typedef struct ecs_scope_iter_t { + ecs_filter_t filter; + ecs_map_iter_t tables; + int32_t index; +} ecs_scope_iter_t; + +/** Term-iterator specific data */ +typedef struct ecs_term_iter_t { + ecs_term_t *term; + ecs_id_record_t *self_index; + ecs_id_record_t *set_index; + + ecs_map_iter_t iter; + bool iter_set; + + /* Storage */ + ecs_id_t id; + int32_t column; + ecs_type_t type; + ecs_entity_t subject; + ecs_size_t size; + void *ptr; +} ecs_term_iter_t; + +typedef enum ecs_filter_iter_kind_t { + EcsFilterIterEvalIndex, + EcsFilterIterEvalNone +} ecs_filter_iter_kind_t; + +/** Filter-iterator specific data */ +typedef struct ecs_filter_iter_t { + ecs_filter_t filter; + ecs_filter_iter_kind_t kind; + + /* For EcsFilterIterEvalIndex */ + ecs_term_iter_t term_iter; + int32_t min_term_index; +} ecs_filter_iter_t; + +/** Query-iterator specific data */ +typedef struct ecs_query_iter_t { + ecs_page_iter_t page_iter; + int32_t index; + int32_t sparse_smallest; + int32_t sparse_first; + int32_t bitset_first; +} ecs_query_iter_t; + +/** Query-iterator specific data */ +typedef struct ecs_snapshot_iter_t { + ecs_filter_t filter; + ecs_vector_t *tables; /* ecs_table_leaf_t */ + int32_t index; +} ecs_snapshot_iter_t; + +/* Inline arrays for queries with small number of components */ +typedef struct ecs_iter_cache_t { + ecs_id_t ids[ECS_TERM_CACHE_SIZE]; + ecs_type_t types[ECS_TERM_CACHE_SIZE]; + int32_t columns[ECS_TERM_CACHE_SIZE]; + ecs_entity_t subjects[ECS_TERM_CACHE_SIZE]; + ecs_size_t sizes[ECS_TERM_CACHE_SIZE]; + void *ptrs[ECS_TERM_CACHE_SIZE]; + + bool ids_alloc; + bool types_alloc; + bool columns_alloc; + bool subjects_alloc; + bool sizes_alloc; + bool ptrs_alloc; +} ecs_iter_cache_t; + +/** The ecs_iter_t struct allows applications to iterate tables. + * Queries and filters, among others, allow an application to iterate entities + * that match a certain set of components. Because of how data is stored + * internally, entities with a given set of components may be stored in multiple + * consecutive arrays, stored across multiple tables. The ecs_iter_t type + * enables iteration across tables. */ +struct ecs_iter_t { + ecs_world_t *world; /**< The world */ + ecs_world_t *real_world; /**< Actual world. This differs from world when using threads. */ + ecs_entity_t system; /**< The current system (if applicable) */ + ecs_entity_t event; /**< The event (if applicable) */ + ecs_id_t event_id; /**< The (component) id for the event */ + ecs_entity_t self; /**< Self entity (if set) */ + + ecs_table_t *table; /**< Current table */ + ecs_data_t *data; + + ecs_id_t *ids; + ecs_type_t *types; + int32_t *columns; + ecs_entity_t *subjects; + ecs_size_t *sizes; + void **ptrs; + + ecs_ref_t *references; + + ecs_query_t *query; /**< Current query being evaluated */ + int32_t table_count; /**< Active table count for query */ + int32_t inactive_table_count; /**< Inactive table count for query */ + int32_t column_count; /**< Number of columns for system */ + int32_t term_index; /**< Index of term that triggered an event. + * This field will be set to the 'index' field + * of a trigger/observer term. */ + + void *table_columns; /**< Table component data */ + ecs_entity_t *entities; /**< Entity identifiers */ + + void *param; /**< Param passed to ecs_run */ + void *ctx; /**< System context */ + void *binding_ctx; /**< Binding context */ + FLECS_FLOAT delta_time; /**< Time elapsed since last frame */ + FLECS_FLOAT delta_system_time;/**< Time elapsed since last system invocation */ + FLECS_FLOAT world_time; /**< Time elapsed since start of simulation */ + + int32_t frame_offset; /**< Offset relative to frame */ + int32_t offset; /**< Offset relative to current table */ + int32_t count; /**< Number of entities to process by system */ + int32_t total_count; /**< Total number of entities in table */ + + bool is_valid; /**< Set to true after first next() */ + + ecs_ids_t *triggered_by; /**< Component(s) that triggered the system */ + ecs_entity_t interrupted_by; /**< When set, system execution is interrupted */ + + union { + ecs_scope_iter_t parent; + ecs_term_iter_t term; + ecs_filter_iter_t filter; + ecs_query_iter_t query; + ecs_snapshot_iter_t snapshot; + } iter; /**< Iterator specific data */ + + ecs_iter_cache_t cache; /**< Inline arrays to reduce allocations */ +}; + +typedef enum EcsMatchFailureReason { + EcsMatchOk, + EcsMatchNotASystem, + EcsMatchSystemIsATask, + EcsMatchEntityIsDisabled, + EcsMatchEntityIsPrefab, + EcsMatchFromSelf, + EcsMatchFromOwned, + EcsMatchFromShared, + EcsMatchFromContainer, + EcsMatchFromEntity, + EcsMatchOrFromSelf, + EcsMatchOrFromOwned, + EcsMatchOrFromShared, + EcsMatchOrFromContainer, + EcsMatchNotFromSelf, + EcsMatchNotFromOwned, + EcsMatchNotFromShared, + EcsMatchNotFromContainer, +} EcsMatchFailureReason; + +typedef struct ecs_match_failure_t { + EcsMatchFailureReason reason; + int32_t column; +} ecs_match_failure_t; + +//////////////////////////////////////////////////////////////////////////////// +//// Function types +//////////////////////////////////////////////////////////////////////////////// + +typedef struct EcsComponentLifecycle EcsComponentLifecycle; + +/** Constructor/destructor. Used for initializing / deinitializing components. */ +typedef void (*ecs_xtor_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx); + +/** Copy is invoked when a component is copied into another component. */ +typedef void (*ecs_copy_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Move is invoked when a component is moved to another component. */ +typedef void (*ecs_move_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Copy ctor */ +typedef void (*ecs_copy_ctor_t)( + ecs_world_t *world, + ecs_entity_t component, + const EcsComponentLifecycle *callbacks, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Move ctor */ +typedef void (*ecs_move_ctor_t)( + ecs_world_t *world, + ecs_entity_t component, + const EcsComponentLifecycle *callbacks, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx); + +/** Invoked when setting a component */ +typedef void (*ecs_on_set_t)( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/include/flecs/private/bitset.h b/fggl/ecs2/flecs/include/flecs/private/bitset.h new file mode 100644 index 0000000000000000000000000000000000000000..b73ab947146bedd9df35f2fca8dfb1410b57719f --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/bitset.h @@ -0,0 +1,81 @@ +/** + * @file bitset.h + * @brief Bitset datastructure. + * + * Simple bitset implementation. The bitset allows for storage of arbitrary + * numbers of bits. + */ + +#ifndef FLECS_BITSET_H +#define FLECS_BITSET_H + +#include "flecs/private/api_defines.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); + +/** Deinialize bitset. */ +FLECS_DBG_API +void flecs_bitset_deinit( + 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 diff --git a/fggl/ecs2/flecs/include/flecs/private/hashmap.h b/fggl/ecs2/flecs/include/flecs/private/hashmap.h new file mode 100644 index 0000000000000000000000000000000000000000..dded83916215b022e2fc1945983a036de75fd33d --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/hashmap.h @@ -0,0 +1,125 @@ +/** + * @file hashmap.h + * @brief Hashmap datastructure. + * + * Datastructure that computes a hash to store & retrieve values. Similar to + * ecs_map_t, but allows for arbitrary keytypes. + */ + +#ifndef FLECS_HASHMAP_H +#define FLECS_HASHMAP_H + +#include "api_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + ecs_hash_value_action_t hash; + ecs_compare_action_t compare; + ecs_size_t key_size; + ecs_size_t value_size; + ecs_map_t *impl; +} ecs_hashmap_t; + +typedef struct { + ecs_map_iter_t it; + struct 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 +ecs_hashmap_t _flecs_hashmap_new( + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare); + +#define flecs_hashmap_new(K, V, compare, hash)\ + _flecs_hashmap_new(ECS_SIZEOF(K), ECS_SIZEOF(V), compare, hash) + +FLECS_DBG_API +void flecs_hashmap_free( + 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( + const ecs_hashmap_t map, + ecs_size_t key_size, + 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( + const 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( + const 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( + const 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 +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 diff --git a/fggl/ecs2/flecs/include/flecs/private/log.h b/fggl/ecs2/flecs/include/flecs/private/log.h new file mode 100644 index 0000000000000000000000000000000000000000..c7b50fe27dfb17918d8f9eaddae41e4c10328b2f --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/log.h @@ -0,0 +1,189 @@ +/** + * @file log.h + * @brief Internal logging API. + * + * Internal utility functions for tracing, warnings and errors. + */ + +#ifndef FLECS_LOG_H +#define FLECS_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// Color macro's +//////////////////////////////////////////////////////////////////////////////// + +#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" + + +//////////////////////////////////////////////////////////////////////////////// +//// Tracing +//////////////////////////////////////////////////////////////////////////////// + +FLECS_API +void _ecs_trace( + int level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_warn( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_err( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_fatal( + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_deprecated( + const char *file, + int32_t line, + const char *msg); + +FLECS_API +void ecs_log_push(void); + +FLECS_API +void ecs_log_pop(void); + +#ifndef FLECS_LEGACY + +#define ecs_trace(lvl, ...)\ + _ecs_trace(lvl, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_warn(...)\ + _ecs_warn(__FILE__, __LINE__, __VA_ARGS__) + +#define ecs_err(...)\ + _ecs_err(__FILE__, __LINE__, __VA_ARGS__) + +#define ecs_fatal(...)\ + _ecs_fatal(__FILE__, __LINE__, __VA_ARGS__) + +#ifndef FLECS_NO_DEPRECATED_WARNINGS +#define ecs_deprecated(...)\ + _ecs_deprecated(__FILE__, __LINE__, __VA_ARGS__) +#else +#define ecs_deprecated(...) +#endif + +/* If no tracing verbosity is defined, pick default based on build config */ +#if !(defined(ECS_TRACE_0) || defined(ECS_TRACE_1) || defined(ECS_TRACE_2) || defined(ECS_TRACE_3)) +#if !defined(NDEBUG) +#define ECS_TRACE_3 /* Enable all tracing in debug mode. May slow things down */ +#else +#define ECS_TRACE_1 /* Only enable infrequent tracing in release mode */ +#endif +#endif + +#if defined(ECS_TRACE_3) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) ecs_trace(2, __VA_ARGS__); +#define ecs_trace_3(...) ecs_trace(3, __VA_ARGS__); + +#elif defined(ECS_TRACE_2) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) ecs_trace(2, __VA_ARGS__); +#define ecs_trace_3(...) + +#elif defined(ECS_TRACE_1) +#define ecs_trace_1(...) ecs_trace(1, __VA_ARGS__); +#define ecs_trace_2(...) +#define ecs_trace_3(...) +#endif +#else +#define ecs_trace_1(...) +#define ecs_trace_2(...) +#define ecs_trace_3(...) +#endif + +//////////////////////////////////////////////////////////////////////////////// +//// Exceptions +//////////////////////////////////////////////////////////////////////////////// + +/** Get description for error code */ +FLECS_API +const char* ecs_strerror( + int32_t error_code); + +/** Abort */ +FLECS_API +void _ecs_abort( + int32_t error_code, + const char *file, + int32_t line, + const char *fmt, + ...); + +#define ecs_abort(error_code, ...)\ + _ecs_abort(error_code, __FILE__, __LINE__, __VA_ARGS__); abort() + +/** Assert */ +FLECS_API +void _ecs_assert( + bool condition, + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...); + +#ifdef NDEBUG +#define ecs_assert(condition, error_code, ...) +#else +#define ecs_assert(condition, error_code, ...)\ + _ecs_assert(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ + assert(condition) +#endif + +FLECS_API +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +#ifndef FLECS_LEGACY + +#define ecs_parser_error(name, expr, column, ...)\ + _ecs_parser_error(name, expr, column, __VA_ARGS__);\ + abort() + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/map.h b/fggl/ecs2/flecs/include/flecs/private/map.h new file mode 100644 index 0000000000000000000000000000000000000000..d468cc636374b155f5db9a2ccd789bbc33c3eafa --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/map.h @@ -0,0 +1,255 @@ +/** + * @file map.h + * @brief Map datastructure. + * + * Key-value datastructure. The map allows for fast retrieval of a payload for + * a 64-bit key. While it is not as fast as the sparse set, it is better at + * handling randomly distributed values. + * + * Payload is stored in bucket arrays. A bucket is computed from an id by + * using the (bucket_count - 1) as an AND-mask. The number of buckets is always + * a power of 2. Multiple keys will be stored in the same bucket. As a result + * the worst case retrieval performance of the map is O(n), though this is rare. + * On average lookup performance should equal O(1). + * + * The datastructure will automatically grow the number of buckets when the + * ratio between elements and buckets exceeds a certain threshold (LOAD_FACTOR). + * + * Note that while the implementation is a hashmap, it can only compute hashes + * for the provided 64 bit keys. This means that the provided keys must always + * be unique. If the provided keys are hashes themselves, it is the + * responsibility of the user to ensure that collisions are handled. + * + * In debug mode the map verifies that the type provided to the map functions + * matches the one used at creation time. + */ + +#ifndef FLECS_MAP_H +#define FLECS_MAP_H + +#include "api_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ecs_map_t ecs_map_t; +typedef uint64_t ecs_map_key_t; + +typedef struct ecs_map_iter_t { + const ecs_map_t *map; + struct ecs_bucket_t *bucket; + int32_t bucket_index; + int32_t element_index; + void *payload; +} ecs_map_iter_t; + +/** Create new map. */ +FLECS_API +ecs_map_t * _ecs_map_new( + ecs_size_t elem_size, + ecs_size_t alignment, + int32_t elem_count); + +#define ecs_map_new(T, elem_count)\ + _ecs_map_new(sizeof(T), ECS_ALIGNOF(T), elem_count) + +/** Get element for key, returns NULL if they key doesn't exist. */ +FLECS_API +void * _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +#define ecs_map_get(map, T, key)\ + (T*)_ecs_map_get(map, sizeof(T), (ecs_map_key_t)key) + +/** Get pointer element. This dereferences the map element as a pointer. This + * operation returns NULL when either the element does not exist or whether the + * pointer is NULL, and should therefore only be used when the application knows + * for sure that a pointer should never be NULL. */ +FLECS_API +void * _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key); + +#define ecs_map_get_ptr(map, T, key)\ + (T)_ecs_map_get_ptr(map, key) + +/** Test if map has key */ +FLECS_API +bool ecs_map_has( + const ecs_map_t *map, + ecs_map_key_t key); + +/** Get or create element for key. */ +FLECS_API +void * _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); + +#define ecs_map_ensure(map, T, key)\ + (T*)_ecs_map_ensure(map, sizeof(T), (ecs_map_key_t)key) + +/** Set element. */ +FLECS_API +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload); + +#define ecs_map_set(map, key, payload)\ + _ecs_map_set(map, sizeof(*payload), (ecs_map_key_t)key, payload); + +/** Free map. */ +FLECS_API +void ecs_map_free( + ecs_map_t *map); + +/** Remove key from map. */ +FLECS_API +void ecs_map_remove( + 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. */ +FLECS_API +int32_t ecs_map_count( + const ecs_map_t *map); + +/** Return number of buckets in map. */ +FLECS_API +int32_t ecs_map_bucket_count( + const ecs_map_t *map); + +/** Return iterator to map contents. */ +FLECS_API +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map); + +/** Obtain next element in map from iterator. */ +FLECS_API +void* _ecs_map_next( + ecs_map_iter_t* iter, + ecs_size_t elem_size, + ecs_map_key_t *key); + +#define ecs_map_next(iter, T, key) \ + (T*)_ecs_map_next(iter, sizeof(T), key) + +/** Obtain next pointer element from iterator. See ecs_map_get_ptr. */ +FLECS_API +void* _ecs_map_next_ptr( + ecs_map_iter_t* iter, + ecs_map_key_t *key); + +#define ecs_map_next_ptr(iter, T, key) \ + (T)_ecs_map_next_ptr(iter, key) + +/** Grow number of buckets in the map for specified number of elements. */ +FLECS_API +void ecs_map_grow( + ecs_map_t *map, + int32_t elem_count); + +/** Set number of buckets in the map for specified number of elements. */ +FLECS_API +void ecs_map_set_size( + ecs_map_t *map, + int32_t elem_count); + +/** Return memory occupied by map. */ +FLECS_API +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used); + +#ifndef FLECS_LEGACY +#define ecs_map_each(map, T, key, var, ...)\ + {\ + ecs_map_iter_t it = ecs_map_iter(map);\ + ecs_map_key_t key;\ + T* var;\ + (void)key;\ + (void)var;\ + while ((var = ecs_map_next(&it, T, &key))) {\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + +/** C++ wrapper for map. */ +#ifdef __cplusplus +#ifndef FLECS_NO_CPP + +#include <initializer_list> +#include <utility> + +namespace flecs { + +/* C++ class mainly used as wrapper around internal ecs_map_t. Do not use + * this class as a replacement for STL datastructures! */ +template <typename K, typename T> +class map { +public: + map(size_t count = 0) { + init(count); + } + + map(std::initializer_list<std::pair<K, T>> elems) { + init(elems.size()); + *this = elems; + } + + void operator=(std::initializer_list<std::pair<K, T>> elems) { + for (auto elem : elems) { + this->set(elem.first, elem.second); + } + } + + void clear() { + ecs_map_clear(m_map); + } + + int32_t count() { + return ecs_map_count(m_map); + } + + void set(K& key, T& value) { + _ecs_map_set(m_map, sizeof(T), reinterpret_cast<ecs_map_key_t>(key), &value); + } + + T& get(K& key) { + static_cast<T*>(_ecs_map_get(m_map, sizeof(T), + reinterpret_cast<ecs_map_key_t>(key))); + } + + void destruct() { + ecs_map_free(m_map); + } + +private: + void init(size_t count) { + m_map = ecs_map_new(T, static_cast<ecs_size_t>(count)); + } + + ecs_map_t *m_map; +}; + +} + +#endif +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/sparse.h b/fggl/ecs2/flecs/include/flecs/private/sparse.h new file mode 100644 index 0000000000000000000000000000000000000000..b2e68a01c7bf89e8dedc09e9f97997d06c794902 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/sparse.h @@ -0,0 +1,279 @@ +/** + * @file sparse.h + * @brief Sparse set datastructure. + * + * This is an implementation of a paged sparse set that stores the payload in + * the sparse array. + * + * A sparse set has a dense and a sparse array. The sparse array is directly + * indexed by a 64 bit identifier. The sparse element is linked with a dense + * element, which allows for liveliness checking. The liveliness check itself + * can be performed by doing (psuedo code): + * dense[sparse[sparse_id].dense] == sparse_id + * + * To ensure that the sparse array doesn't have to grow to a large size when + * using large sparse_id's, the sparse set uses paging. This cuts up the array + * into several pages of 4096 elements. When an element is set, the sparse set + * ensures that the corresponding page is created. The page associated with an + * id is determined by shifting a bit 12 bits to the right. + * + * The sparse set keeps track of a generation count per id, which is increased + * each time an id is deleted. The generation is encoded in the returned id. + * + * This sparse set implementation stores payload in the sparse array, which is + * not typical. The reason for this is to guarantee that (in combination with + * paging) the returned payload pointers are stable. This allows for various + * optimizations in the parts of the framework that uses the sparse set. + * + * The sparse set has been designed so that new ids can be generated in bulk, in + * an O(1) operation. The way this works is that once a dense-sparse pair is + * created, it is never unpaired. Instead it is moved to the end of the dense + * array, and the sparse set stores an additional count to keep track of the + * last alive id in the sparse set. To generate new ids in bulk, the sparse set + * only needs to increase this count by the number of requested ids. + */ + +#ifndef FLECS_SPARSE_H +#define FLECS_SPARSE_H + +#include "flecs/private/api_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Create new sparse set */ +FLECS_DBG_API +ecs_sparse_t* _flecs_sparse_new( + ecs_size_t elem_size); + +#define flecs_sparse_new(type)\ + _flecs_sparse_new(sizeof(type)) + +/** Set id source. This allows the sparse set to use an external variable for + * issuing and increasing new ids. */ +FLECS_DBG_API +void flecs_sparse_set_id_source( + ecs_sparse_t *sparse, + uint64_t *id_source); + +/** Free sparse set */ +FLECS_DBG_API +void flecs_sparse_free( + 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(sparse, type)\ + ((type*)_flecs_sparse_add(sparse, sizeof(type))) + +/** 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); + +/** Generate or recycle new ids in bulk. The returned pointer points directly to + * the internal dense array vector with sparse ids. Operations on the sparse set + * can (and likely will) modify the contents of the buffer. */ +FLECS_DBG_API +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t count); + +/** Remove an element */ +FLECS_DBG_API +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t id); + +/** Remove an element, return pointer to the value in the sparse array */ +FLECS_DBG_API +void* _flecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_remove_get(sparse, type, index)\ + ((type*)_flecs_sparse_remove_get(sparse, sizeof(type), index)) + +/** Override the generation count for a specific id */ +FLECS_DBG_API +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t id); + +/** Check whether an id has ever been issued. */ +FLECS_DBG_API +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Test if id is alive, which requires the generation count tp match. */ +FLECS_DBG_API +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Return identifier with current generation set. */ +FLECS_DBG_API +uint64_t flecs_sparse_get_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(sparse, type, index)\ + ((type*)_flecs_sparse_get_dense(sparse, sizeof(type), index)) + +/** Get the number of alive elements in the sparse set. */ +FLECS_DBG_API +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse); + +/** Return total number of allocated elements in the dense array */ +FLECS_DBG_API +int32_t flecs_sparse_size( + 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(sparse, type, index)\ + ((type*)_flecs_sparse_get(sparse, sizeof(type), index)) + +/** Like get_sparse, but don't care whether element is alive or not. */ +FLECS_DBG_API +void* _flecs_sparse_get_any( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_get_any(sparse, type, index)\ + ((type*)_flecs_sparse_get_any(sparse, sizeof(type), 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(sparse, type, index)\ + ((type*)_flecs_sparse_ensure(sparse, sizeof(type), index)) + +/** Set value. */ +FLECS_DBG_API +void* _flecs_sparse_set( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id, + void *value); + +#define flecs_sparse_set(sparse, type, index, value)\ + ((type*)_flecs_sparse_set(sparse, sizeof(type), index, value)) + +/** Get pointer to ids (alive and not alive). Use with count() or size(). */ +FLECS_DBG_API +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse); + +/** Set size of the dense array. */ +FLECS_DBG_API +void flecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count); + +/** Copy sparse set into a new sparse set. */ +FLECS_DBG_API +ecs_sparse_t* flecs_sparse_copy( + const ecs_sparse_t *src); + +/** Restore sparse set into destination sparse set. */ +FLECS_DBG_API +void flecs_sparse_restore( + ecs_sparse_t *dst, + const ecs_sparse_t *src); + +/** Get memory usage of sparse set. */ +FLECS_DBG_API +void flecs_sparse_memory( + ecs_sparse_t *sparse, + int32_t *allocd, + int32_t *used); + + +/* Publicly exposed APIs + * The flecs_ functions aren't exposed directly as this can cause some + * optimizers to not consider them for link time optimization. */ + +FLECS_API +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size); + +#define ecs_sparse_new(type)\ + _ecs_sparse_new(sizeof(type)) + +FLECS_API +void* _ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_add(sparse, type)\ + ((type*)_ecs_sparse_add(sparse, sizeof(type))) + +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(sparse, type, index)\ + ((type*)_ecs_sparse_get_dense(sparse, sizeof(type), index)) + +FLECS_API +void* _ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define ecs_sparse_get(sparse, type, index)\ + ((type*)_ecs_sparse_get(sparse, sizeof(type), index)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/strbuf.h b/fggl/ecs2/flecs/include/flecs/private/strbuf.h new file mode 100644 index 0000000000000000000000000000000000000000..802501a411eb447c507c4ae5b608de0a91988f42 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/strbuf.h @@ -0,0 +1,174 @@ +/** + * @file strbuf.h + * @brief Utility for constructing strings. + * + * A buffer builds up a list of elements which individually can be up to N bytes + * large. While appending, data is added to these elements. More elements are + * added on the fly when needed. When an application calls ecs_strbuf_get, all + * elements are combined in one string and the element administration is freed. + * + * This approach prevents reallocs of large blocks of memory, and therefore + * copying large blocks of memory when appending to a large buffer. A buffer + * preallocates some memory for the element overhead so that for small strings + * there is hardly any overhead, while for large strings the overhead is offset + * by the reduced time spent on copying memory. + */ + +#ifndef FLECS_STRBUF_H_ +#define FLECS_STRBUF_H_ + +#include "api_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_STRBUF_INIT (ecs_strbuf_t){0} +#define ECS_STRBUF_ELEMENT_SIZE (511) +#define ECS_STRBUF_MAX_LIST_DEPTH (32) + +typedef struct ecs_strbuf_element { + bool buffer_embedded; + int32_t pos; + char *buf; + struct ecs_strbuf_element *next; +} ecs_strbuf_element; + +typedef struct ecs_strbuf_element_embedded { + ecs_strbuf_element super; + char buf[ECS_STRBUF_ELEMENT_SIZE + 1]; +} ecs_strbuf_element_embedded; + +typedef struct ecs_strbuf_element_str { + ecs_strbuf_element super; + char *alloc_str; +} ecs_strbuf_element_str; + +typedef struct ecs_strbuf_list_elem { + int32_t count; + const char *separator; +} ecs_strbuf_list_elem; + +typedef struct ecs_strbuf_t { + /* When set by an application, append will write to this buffer */ + char *buf; + + /* The maximum number of characters that may be printed */ + int32_t max; + + /* Size of elements minus current element */ + int32_t size; + + /* The number of elements in use */ + int32_t elementCount; + + /* Always allocate at least one element */ + ecs_strbuf_element_embedded firstElement; + + /* The current element being appended to */ + ecs_strbuf_element *current; + + /* Stack that keeps track of number of list elements, used for conditionally + * inserting a separator */ + ecs_strbuf_list_elem list_stack[ECS_STRBUF_MAX_LIST_DEPTH]; + int32_t list_sp; +} ecs_strbuf_t; + +/* Append format string to a buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_append( + 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 +bool 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 +bool ecs_strbuf_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +/* Append source buffer to destination buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer); + +/* Append string to buffer, transfer ownership to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *buffer, + char *str); + +/* Append string to buffer, do not free/modify string. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *buffer, + const char *str); + +/* Append n characters to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + +/* Return result string (also resets buffer) */ +FLECS_API +char *ecs_strbuf_get( + 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 formatted string as a new element in list */ +FLECS_API +bool ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...); + +/* Append string as a new element in list */ +FLECS_API +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/switch_list.h b/fggl/ecs2/flecs/include/flecs/private/switch_list.h new file mode 100644 index 0000000000000000000000000000000000000000..c16b445ce8152ece3157e6653a6481cd40ac3196 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/switch_list.h @@ -0,0 +1,140 @@ +/** + * @file switch_list.h + * @brief Interleaved linked list for storing mutually exclusive values. + * + * Datastructure that stores N interleaved linked lists in an array. + * This allows for efficient storage of elements with mutually exclusive values. + * Each linked list has a header element which points to the index in the array + * that stores the first node of the list. Each list node points to the next + * array element. + * + * The datastructure needs to be created with min and max values, so that it can + * allocate an array of headers that can be directly indexed by the value. The + * values are stored in a contiguous array, which allows for the values to be + * iterated without having to follow the linked list nodes. + * + * The datastructure allows for efficient storage and retrieval for values with + * mutually exclusive values, such as enumeration values. The linked list allows + * an application to obtain all elements for a given (enumeration) value without + * having to search. + * + * While the list accepts 64 bit values, it only uses the lower 32bits of the + * value for selecting the correct linked list. + */ + +#ifndef FLECS_SWITCH_LIST_H +#define FLECS_SWITCH_LIST_H + +#include "flecs/private/api_defines.h" + +typedef struct flecs_switch_header_t { + int32_t element; /* First element for value */ + int32_t count; /* Number of elements for value */ +} flecs_switch_header_t; + +typedef struct flecs_switch_node_t { + int32_t next; /* Next node in list */ + int32_t prev; /* Prev node in list */ +} flecs_switch_node_t; + +struct ecs_switch_t { + uint64_t min; /* Minimum value the switch can store */ + uint64_t max; /* Maximum value the switch can store */ + flecs_switch_header_t *headers; /* Array with headers, indexed by value */ + ecs_vector_t *nodes; /* Vector with nodes, of type flecs_switch_node_t */ + ecs_vector_t *values; /* Vector with values, of type uint64_t */ +}; + +/** Create new switch. */ +FLECS_DBG_API +ecs_switch_t* flecs_switch_new( + uint64_t min, + uint64_t max, + int32_t elements); + +/** Free switch. */ +FLECS_DBG_API +void flecs_switch_free( + ecs_switch_t *sw); + +/** Add element to switch, initialize value to 0 */ +FLECS_DBG_API +void flecs_switch_add( + ecs_switch_t *sw); + +/** Set number of elements in switch list */ +FLECS_DBG_API +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count); + +/** Ensure that element exists. */ +FLECS_DBG_API +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count); + +/** Add n elements. */ +FLECS_DBG_API +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count); + +/** Set value of element. */ +FLECS_DBG_API +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value); + +/** Remove element. */ +FLECS_DBG_API +void flecs_switch_remove( + ecs_switch_t *sw, + int32_t element); + +/** Get value for element. */ +FLECS_DBG_API +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element); + +/** Swap element. */ +FLECS_DBG_API +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2); + +/** Get vector with all values. Use together with count(). */ +FLECS_DBG_API +ecs_vector_t* flecs_switch_values( + const ecs_switch_t *sw); + +/** Return number of different values. */ +FLECS_DBG_API +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value); + +/** Return first element for value. */ +FLECS_DBG_API +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value); + +/** Return next element for value. Use with first(). */ +FLECS_DBG_API +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t elem); + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/private/vector.h b/fggl/ecs2/flecs/include/flecs/private/vector.h new file mode 100644 index 0000000000000000000000000000000000000000..e7ec7c70b3b06e98c6f2eaf69ae7f8790a20d55d --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/private/vector.h @@ -0,0 +1,537 @@ + +/** + * @file vector.h + * @brief Vector datastructure. + * + * This is an implementation of a simple vector type. The vector is allocated in + * a single block of memory, with the element count, and allocated number of + * elements encoded in the block. As this vector is used for user-types it has + * been designed to support alignments higher than 8 bytes. This makes the size + * of the vector header variable in size. To reduce the overhead associated with + * retrieving or computing this size, the functions are wrapped in macro calls + * that compute the header size at compile time. + * + * The API provides a number of _t macro's, which accept a size and alignment. + * These macro's are used when no compile-time type is available. + * + * The vector guarantees contiguous access to its elements. When an element is + * removed from the vector, the last element is copied to the removed element. + * + * The API requires passing in the type of the vector. This type is used to test + * whether the size of the provided type equals the size of the type with which + * the vector was created. In release mode this check is not performed. + * + * When elements are added to the vector, it will automatically resize to the + * next power of two. This can change the pointer of the vector, which is why + * operations that can increase the vector size, accept a double pointer to the + * vector. + */ + +#ifndef FLECS_VECTOR_H +#define FLECS_VECTOR_H + +#include "api_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public, so we can do compile-time header size calculation */ +struct ecs_vector_t { + int32_t count; + int32_t size; + +#ifndef NDEBUG + int64_t elem_size; +#endif +}; + +/* Compute the header size of the vector from size & alignment */ +#define ECS_VECTOR_U(size, alignment) size, ECS_CAST(int16_t, ECS_MAX(ECS_SIZEOF(ecs_vector_t), alignment)) + +/* Compute the header size of the vector from a provided compile-time type */ +#define ECS_VECTOR_T(T) ECS_VECTOR_U(ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +/* Utility macro's for creating vector on stack */ +#ifndef NDEBUG +#define ECS_VECTOR_VALUE(T, elem_count)\ +{\ + .elem_size = (int32_t)(ECS_SIZEOF(T)),\ + .count = elem_count,\ + .size = elem_count\ +} +#else +#define ECS_VECTOR_VALUE(T, elem_count)\ +{\ + .count = elem_count,\ + .size = elem_count\ +} +#endif + +#define ECS_VECTOR_DECL(name, T, elem_count)\ +struct {\ + union {\ + ecs_vector_t vector;\ + uint64_t align;\ + } header;\ + T array[elem_count];\ +} __##name##_value = {\ + .header.vector = ECS_VECTOR_VALUE(T, elem_count)\ +};\ +const ecs_vector_t *name = (ecs_vector_t*)&__##name##_value + +#define ECS_VECTOR_IMPL(name, T, elems, elem_count)\ +ecs_os_memcpy(__##name##_value.array, elems, sizeof(T) * elem_count) + +#define ECS_VECTOR_STACK(name, T, elems, elem_count)\ +ECS_VECTOR_DECL(name, T, elem_count);\ +ECS_VECTOR_IMPL(name, T, elems, elem_count) + +typedef struct ecs_vector_t ecs_vector_t; + +typedef int (*ecs_comparator_t)( + const void* p1, + const void *p2); + +/** Create new vector. */ +FLECS_API +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_new(T, elem_count) \ + _ecs_vector_new(ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_new_t(size, alignment, elem_count) \ + _ecs_vector_new(ECS_VECTOR_U(size, alignment), elem_count) + +/* Create new vector, initialize it with provided array */ +FLECS_API +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array); + +#define ecs_vector_from_array(T, elem_count, array)\ + _ecs_vector_from_array(ECS_VECTOR_T(T), elem_count, array) + +/* Initialize vector with zero's */ +FLECS_API +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_zero(vector, T) \ + _ecs_vector_zero(vector, ECS_VECTOR_T(T)) + +/** Free vector */ +FLECS_API +void ecs_vector_free( + ecs_vector_t *vector); + +/** Clear values in vector */ +FLECS_API +void ecs_vector_clear( + ecs_vector_t *vector); + +/** Assert when the provided size does not match the vector type. */ +FLECS_API +void ecs_vector_assert_size( + ecs_vector_t* vector_inout, + ecs_size_t elem_size); + +/** Assert when the provided alignment does not match the vector type. */ +FLECS_API +void ecs_vector_assert_alignment( + ecs_vector_t* vector, + ecs_size_t elem_alignment); + +/** Add element to vector. */ +FLECS_API +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_add(vector, T) \ + ((T*)_ecs_vector_add(vector, ECS_VECTOR_T(T))) + +#define ecs_vector_add_t(vector, size, alignment) \ + _ecs_vector_add(vector, ECS_VECTOR_U(size, alignment)) + +/** Add n elements to the vector. */ +FLECS_API +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_addn(vector, T, elem_count) \ + ((T*)_ecs_vector_addn(vector, ECS_VECTOR_T(T), elem_count)) + +#define ecs_vector_addn_t(vector, size, alignment, elem_count) \ + _ecs_vector_addn(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Get element from vector. */ +FLECS_API +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_get(vector, T, index) \ + ((T*)_ecs_vector_get(vector, ECS_VECTOR_T(T), index)) + +#define ecs_vector_get_t(vector, size, alignment, index) \ + _ecs_vector_get(vector, ECS_VECTOR_U(size, alignment), index) + +/** Get last element from vector. */ +FLECS_API +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_last(vector, T) \ + (T*)_ecs_vector_last(vector, ECS_VECTOR_T(T)) + +#define ecs_vector_last_t(vector, size, alignment) \ + _ecs_vector_last(vector, ECS_VECTOR_U(size, alignment)) + +/** Set minimum size for vector. If the current size of the vector is larger, + * the function will have no side effects. */ +FLECS_API +int32_t _ecs_vector_set_min_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_min_size(vector, T, size) \ + _ecs_vector_set_min_size(vector, ECS_VECTOR_T(T), size) + +/** Set minimum count for vector. If the current count of the vector is larger, + * the function will have no side effects. */ +FLECS_API +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_min_count(vector, T, size) \ + _ecs_vector_set_min_count(vector, ECS_VECTOR_T(T), size) + +/** Remove last element. This operation requires no swapping of values. */ +FLECS_API +void ecs_vector_remove_last( + ecs_vector_t *vector); + +/** Remove last value, store last element in provided value. */ +FLECS_API +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value); + +#define ecs_vector_pop(vector, T, value) \ + _ecs_vector_pop(vector, ECS_VECTOR_T(T), value) + +/** Append element at specified index to another vector. */ +FLECS_API +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_move_index(dst, src, T, index) \ + _ecs_vector_move_index(dst, src, ECS_VECTOR_T(T), index) + +/** Remove element at specified index. Moves the last value to the index. */ +FLECS_API +int32_t _ecs_vector_remove( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index); + +#define ecs_vector_remove(vector, T, index) \ + _ecs_vector_remove(vector, ECS_VECTOR_T(T), index) + +#define ecs_vector_remove_t(vector, size, alignment, index) \ + _ecs_vector_remove(vector, ECS_VECTOR_U(size, alignment), index) + +/** Shrink vector to make the size match the count. */ +FLECS_API +void _ecs_vector_reclaim( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_reclaim(vector, T)\ + _ecs_vector_reclaim(vector, ECS_VECTOR_T(T)) + +/** Grow size of vector with provided number of elements. */ +FLECS_API +int32_t _ecs_vector_grow( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_grow(vector, T, size) \ + _ecs_vector_grow(vector, ECS_VECTOR_T(T), size) + +/** Set allocation size of vector. */ +FLECS_API +int32_t _ecs_vector_set_size( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_size(vector, T, elem_count) \ + _ecs_vector_set_size(vector, ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_set_size_t(vector, size, alignment, elem_count) \ + _ecs_vector_set_size(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Set count of vector. If the size of the vector is smaller than the provided + * count, the vector is resized. */ +FLECS_API +int32_t _ecs_vector_set_count( + ecs_vector_t **vector, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count); + +#define ecs_vector_set_count(vector, T, elem_count) \ + _ecs_vector_set_count(vector, ECS_VECTOR_T(T), elem_count) + +#define ecs_vector_set_count_t(vector, size, alignment, elem_count) \ + _ecs_vector_set_count(vector, ECS_VECTOR_U(size, alignment), elem_count) + +/** Return number of elements in vector. */ +FLECS_API +int32_t ecs_vector_count( + const ecs_vector_t *vector); + +/** Return size of vector. */ +FLECS_API +int32_t ecs_vector_size( + const ecs_vector_t *vector); + +/** Return first element of vector. */ +FLECS_API +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_first(vector, T) \ + ((T*)_ecs_vector_first(vector, ECS_VECTOR_T(T))) + +#define ecs_vector_first_t(vector, size, alignment) \ + _ecs_vector_first(vector, ECS_VECTOR_U(size, alignment)) + +/** Sort elements in vector. */ +FLECS_API +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action); + +#define ecs_vector_sort(vector, T, compare_action) \ + _ecs_vector_sort(vector, ECS_VECTOR_T(T), compare_action) + +/** Return memory occupied by vector. */ +FLECS_API +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used); + +#define ecs_vector_memory(vector, T, allocd, used) \ + _ecs_vector_memory(vector, ECS_VECTOR_T(T), allocd, used) + +#define ecs_vector_memory_t(vector, size, alignment, allocd, used) \ + _ecs_vector_memory(vector, ECS_VECTOR_U(size, alignment), allocd, used) + +/** Copy vectors */ +FLECS_API +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset); + +#define ecs_vector_copy(src, T) \ + _ecs_vector_copy(src, ECS_VECTOR_T(T)) + +#define ecs_vector_copy_t(src, size, alignment) \ + _ecs_vector_copy(src, ECS_VECTOR_U(size, alignment)) + +#ifndef FLECS_LEGACY +#define ecs_vector_each(vector, T, var, ...)\ + {\ + int var##_i, var##_count = ecs_vector_count(vector);\ + T* var##_array = ecs_vector_first(vector, T);\ + for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ + T* var = &var##_array[var##_i];\ + __VA_ARGS__\ + }\ + } +#endif +#ifdef __cplusplus +} +#endif + + +/** C++ wrapper for vector class. */ +#ifdef __cplusplus +#ifndef FLECS_NO_CPP + +#include <initializer_list> + +namespace flecs { + +template <typename T> +class vector_iterator +{ +public: + explicit vector_iterator(T* value, int index) { + m_value = value; + m_index = index; + } + + bool operator!=(vector_iterator const& other) const + { + return m_index != other.m_index; + } + + T const& operator*() const + { + return m_value[m_index]; + } + + vector_iterator& operator++() + { + ++m_index; + return *this; + } + +private: + T* m_value; + int m_index; +}; + +/* C++ class mainly used as wrapper around internal ecs_vector_t. Do not use + * this class as a replacement for STL datastructures! */ +template <typename T> +class vector { +public: + explicit vector(ecs_vector_t *v) : m_vector( v ) { } + + vector(size_t count = 0) : m_vector( nullptr ) { + if (count) { + init(count); + } + } + + vector(std::initializer_list<T> elems) : m_vector( nullptr) { + init(elems.size()); + *this = elems; + } + + void operator=(std::initializer_list<T> elems) { + for (auto elem : elems) { + this->add(elem); + } + } + + T& operator[](size_t index) { + return *static_cast<T*>(_ecs_vector_get(m_vector, ECS_VECTOR_T(T), index)); + } + + vector_iterator<T> begin() { + return vector_iterator<T>( + static_cast<T*>(_ecs_vector_first(m_vector, ECS_VECTOR_T(T))), 0); + } + + vector_iterator<T> end() { + return vector_iterator<T>( + static_cast<T*>(_ecs_vector_last(m_vector, ECS_VECTOR_T(T))), + ecs_vector_count(m_vector)); + } + + void clear() { + ecs_vector_clear(m_vector); + } + + void destruct() { + ecs_vector_free(m_vector); + } + + void add(T& value) { + T* elem = static_cast<T*>(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))); + *elem = value; + } + + void add(T&& value) { + T* elem = static_cast<T*>(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))) + *elem = value; + } + + T& get(int32_t index) { + ecs_assert(index < ecs_vector_count(m_vector), ECS_OUT_OF_RANGE, NULL); + return *static_cast<T*>(_ecs_vector_get(m_vector, ECS_VECTOR_T(T), index)); + } + + T& first() { + return *static_cast<T*>(_ecs_vector_first(m_vector, ECS_VECTOR_T(T))); + } + + T& last() { + return *static_cast<T*>(_ecs_vector_last(m_vector, ECS_VECTOR_T(T))); + } + + int32_t count() { + return ecs_vector_count(m_vector); + } + + int32_t size() { + return ecs_vector_size(m_vector); + } + + ecs_vector_t *ptr() { + return m_vector; + } + + void ptr(ecs_vector_t *ptr) { + m_vector = ptr; + } + +private: + void init(size_t count) { + m_vector = ecs_vector_new(T, static_cast<ecs_size_t>(count)); + } + + ecs_vector_t *m_vector; +}; + +} + +#endif +#endif + +#endif diff --git a/fggl/ecs2/flecs/include/flecs/type.h b/fggl/ecs2/flecs/include/flecs/type.h new file mode 100644 index 0000000000000000000000000000000000000000..02830b067355eb13457853b6fd83607a54491522 --- /dev/null +++ b/fggl/ecs2/flecs/include/flecs/type.h @@ -0,0 +1,103 @@ +/** + * @file type.h + * @brief Type API. + * + * This API contains utilities for working with types. Types are vectors of + * component ids, and are used most prominently in the API to construct filters. + */ + +#ifndef FLECS_TYPE_H +#define FLECS_TYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API +ecs_type_t ecs_type_from_id( + ecs_world_t *world, + ecs_entity_t entity); + +FLECS_API +ecs_entity_t ecs_type_to_id( + const ecs_world_t *world, + ecs_type_t type); + +FLECS_API +char* ecs_type_str( + const ecs_world_t *world, + ecs_type_t type); + +FLECS_API +ecs_type_t ecs_type_from_str( + ecs_world_t *world, + const char *expr); + +FLECS_API +ecs_type_t ecs_type_merge( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t type_add, + ecs_type_t type_remove); + +FLECS_API +ecs_type_t ecs_type_add( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id); + +FLECS_API +ecs_type_t ecs_type_remove( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id); + +FLECS_API +int32_t ecs_type_index_of( + ecs_type_t type, + int32_t offset, + ecs_id_t id); + +FLECS_API +bool ecs_type_has_id( + const ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + bool owned); + +FLECS_API +int32_t ecs_type_match( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + ecs_entity_t *out); + +FLECS_API +bool ecs_type_has_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has); + +FLECS_API +bool ecs_type_owns_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has, + bool owned); + +FLECS_API +ecs_entity_t ecs_type_get_entity_for_xor( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t xor_tag); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/meson.build b/fggl/ecs2/flecs/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..587f776fe37a933741fc75df57fbaf8c9125a666 --- /dev/null +++ b/fggl/ecs2/flecs/meson.build @@ -0,0 +1,86 @@ +project('flecs', 'c', license : 'mit', default_options : ['c_std=c99']) + +flecs_args = [] + +if get_option('default_library') == 'static' + flecs_args = '-Dflecs_STATIC' +endif + +flecs_inc = include_directories('include') + +flecs_deps = dependency('threads') + +flecs_src = files( + 'src/addons/bulk.c', + 'src/addons/deprecated.c', + 'src/addons/direct_access.c', + 'src/addons/module.c', + 'src/addons/parser.c', + 'src/addons/queue.c', + 'src/addons/snapshot.c', + 'src/addons/stats.c', + 'src/modules/pipeline/pipeline.c', + 'src/modules/pipeline/worker.c', + 'src/modules/system/system.c', + 'src/modules/timer.c', + 'src/api_support.c', + 'src/bitset.c', + 'src/bootstrap.c', + 'src/entity.c', + 'src/filter.c', + 'src/hash.c', + 'src/hashmap.c', + 'src/hierarchy.c', + 'src/iter.c', + 'src/log.c', + 'src/map.c', + 'src/misc.c', + 'src/observer.c', + 'src/os_api.c', + 'src/query.c', + 'src/sparse.c', + 'src/stage.c', + 'src/strbuf.c', + 'src/switch_list.c', + 'src/table_graph.c', + 'src/table.c', + 'src/trigger.c', + 'src/type.c', + 'src/vector.c', + 'src/world.c', +) + +install_headers('include/flecs.h') +install_subdir('include/flecs', install_dir : get_option('includedir')) + +flecs_lib = library('flecs', + flecs_src, + install : true, + c_args : [ '-Dflecs_EXPORTS', flecs_args ], + dependencies : flecs_deps, + include_directories : flecs_inc, + implicit_include_directories : false +) + +flecs_dep = declare_dependency( + link_with : flecs_lib, + compile_args : flecs_args, + dependencies : flecs_deps, + include_directories : flecs_inc +) + +helloworld_inc = include_directories('examples/c/01_helloworld/include') + +helloworld_exe = executable('helloworld', + 'examples/c/01_helloworld/src/main.c', + include_directories : helloworld_inc, + implicit_include_directories : false, + dependencies : flecs_dep +) + +if meson.version().version_compare('>= 0.54.0') + meson.override_dependency('flecs', flecs_dep) +endif + +pkg = import('pkgconfig') +pkg.generate(flecs_lib) diff --git a/fggl/ecs2/flecs/project.json b/fggl/ecs2/flecs/project.json new file mode 100644 index 0000000000000000000000000000000000000000..8be8d09cf044925ca9f124845d3fe2608980f613 --- /dev/null +++ b/fggl/ecs2/flecs/project.json @@ -0,0 +1,14 @@ +{ + "id":"flecs", + "type":"package", + "value": { + "author": "Sander Mertens", + "description": "Multithreaded Entity Component System written in C99", + "amalgamate": true + }, + "lang.c": { + "${os linux}": { + "lib": ["rt"] + } + } +} diff --git a/fggl/ecs2/flecs/src/addons/bulk.c b/fggl/ecs2/flecs/src/addons/bulk.c new file mode 100644 index 0000000000000000000000000000000000000000..e0890ec56fd3f9d3aabc6ae1d1dc883d94ec12ca --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/bulk.c @@ -0,0 +1,332 @@ +#include "flecs.h" + +#ifdef FLECS_BULK + +#include "../private_api.h" + +static +void bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter, + bool is_delete) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + int32_t i, count = flecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + /* Remove entities from index */ + ecs_data_t *data = flecs_table_get_data(table); + if (!data) { + /* If table has no data, there's nothing to delete */ + continue; + } + + /* Both filters passed, clear table */ + if (is_delete) { + flecs_table_delete_entities(world, table); + } else { + flecs_table_clear_entities_silent(world, table); + } + } +} + +static +void merge_table( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + ecs_ids_t *to_add, + ecs_ids_t *to_remove) +{ + if (!dst_table->type) { + /* If this removes all components, clear table */ + flecs_table_clear_entities(world, src_table); + } else { + /* Merge table into dst_table */ + if (dst_table != src_table) { + ecs_data_t *src_data = flecs_table_get_data(src_table); + int32_t dst_count = ecs_table_count(dst_table); + int32_t src_count = ecs_table_count(src_table); + + if (to_remove && to_remove->count && src_data) { + flecs_run_remove_actions(world, src_table, + src_data, 0, src_count, to_remove); + } + + ecs_data_t *dst_data = flecs_table_get_data(dst_table); + dst_data = flecs_table_merge( + world, dst_table, src_table, dst_data, src_data); + + if (to_add && to_add->count && dst_data) { + flecs_run_add_actions(world, dst_table, dst_data, + dst_count, src_count, to_add, false, true); + } + } + } +} + +/* -- Public API -- */ + +void ecs_bulk_delete( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + bulk_delete(world, filter, true); +} + +void ecs_bulk_add_remove_type( + ecs_world_t *world, + ecs_type_t to_add, + ecs_type_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_add_array = flecs_type_to_ids(to_add); + ecs_ids_t to_remove_array = flecs_type_to_ids(to_remove); + + ecs_ids_t added = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count), + .count = 0 + }; + + ecs_ids_t removed = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count), + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + dst_table = flecs_table_traverse_add( + world, dst_table, &to_add_array, &added); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (table == dst_table || (!added.count && !removed.count)) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + merge_table(world, dst_table, table, &added, &removed); + added.count = 0; + removed.count = 0; + } +} + +void ecs_bulk_add_type( + ecs_world_t *world, + ecs_type_t to_add, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_add_array = flecs_type_to_ids(to_add); + ecs_ids_t added = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_add_array.count), + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_add( + world, table, &to_add_array, &added); + + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!added.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, &added, NULL); + added.count = 0; + } +} + +void ecs_bulk_add_entity( + ecs_world_t *world, + ecs_entity_t to_add, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_add != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_add_array = { .array = &to_add, .count = 1 }; + + ecs_entity_t added_entity; + ecs_ids_t added = { + .array = &added_entity, + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_add( + world, table, &to_add_array, &added); + + ecs_assert(added.count <= to_add_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!added.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, &added, NULL); + added.count = 0; + } +} + +void ecs_bulk_remove_type( + ecs_world_t *world, + ecs_type_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_remove_array = flecs_type_to_ids(to_remove); + ecs_ids_t removed = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * to_remove_array.count), + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!removed.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, NULL, &removed); + removed.count = 0; + } +} + +void ecs_bulk_remove_entity( + ecs_world_t *world, + ecs_entity_t to_remove, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(stage == &world->stage, ECS_UNSUPPORTED, NULL); + (void)stage; + + ecs_ids_t to_remove_array = { .array = &to_remove, .count = 1 }; + + ecs_entity_t removed_entity; + ecs_ids_t removed = { + .array = &removed_entity, + .count = 0 + }; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + if (!flecs_table_match_filter(world, table, filter)) { + continue; + } + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, table, &to_remove_array, &removed); + + ecs_assert(removed.count <= to_remove_array.count, ECS_INTERNAL_ERROR, NULL); + + if (!removed.count) { + continue; + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + merge_table(world, dst_table, table, NULL, &removed); + removed.count = 0; + } +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/deprecated.c b/fggl/ecs2/flecs/src/addons/deprecated.c new file mode 100644 index 0000000000000000000000000000000000000000..cdfdbcb983fcdfec113cc6da5033cf2b8417859f --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/deprecated.c @@ -0,0 +1,346 @@ +#include "flecs.h" + +#ifdef FLECS_DEPRECATED + +#include "../private_api.h" + +int32_t ecs_count_w_filter( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + return ecs_count_filter(world, filter); +} + +int32_t ecs_count_entity( + const ecs_world_t *world, + ecs_id_t entity) +{ + return ecs_count_id(world, entity); +} + +void ecs_set_component_actions_w_entity( + ecs_world_t *world, + ecs_id_t id, + EcsComponentLifecycle *actions) +{ + ecs_set_component_actions_w_id(world, id, actions); +} + +ecs_entity_t ecs_new_w_entity( + ecs_world_t *world, + ecs_id_t id) +{ + return ecs_new_w_id(world, id); +} + +const ecs_entity_t* ecs_bulk_new_w_entity( + ecs_world_t *world, + ecs_id_t id, + int32_t count) +{ + return ecs_bulk_new_w_id(world, id, count); +} + +void ecs_enable_component_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + ecs_enable_component_w_id(world, entity, id, enable); +} + +bool ecs_is_component_enabled_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_is_component_enabled_w_id(world, entity, id); +} + +const void* ecs_get_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_get_id(world, entity, id); +} + +const void* ecs_get_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_get_id(world, entity, id); +} + +const void* ecs_get_ref_w_entity( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_get_ref_w_id(world, ref, entity, id); +} + +void* ecs_get_mut_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added) +{ + return ecs_get_mut_id(world, entity, id, is_added); +} + +void* ecs_get_mut_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool *is_added) +{ + return ecs_get_mut_id(world, entity, id, is_added); +} + +void ecs_modified_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_modified_id(world, entity, id); +} + +void ecs_modified_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_modified_id(world, entity, id); +} + +ecs_entity_t ecs_set_ptr_w_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) +{ + return ecs_set_id(world, entity, id, size, ptr); +} + +bool ecs_has_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + return ecs_has_id(world, entity, id); +} + +size_t ecs_entity_str( + const ecs_world_t *world, + ecs_id_t entity, + char *buffer, + size_t buffer_len) +{ + return ecs_id_str(world, entity, buffer, buffer_len); +} + +ecs_entity_t ecs_get_parent_w_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + (void)id; + return ecs_get_object(world, entity, EcsChildOf, 0); +} + +int32_t ecs_get_thread_index( + const ecs_world_t *world) +{ + return ecs_get_stage_id(world); +} + +void ecs_add_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_add_id(world, entity, id); +} + +void ecs_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_remove_id(world, entity, id); +} + +void ecs_dim_type( + ecs_world_t *world, + ecs_type_t type, + int32_t entity_count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + if (type) { + ecs_table_t *table = ecs_table_from_type(world, type); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = flecs_table_get_or_create_data(table); + flecs_table_set_size(world, table, data, entity_count); + } +} + +ecs_type_t ecs_type_from_entity( + ecs_world_t *world, + ecs_entity_t entity) +{ + return ecs_type_from_id(world, entity); +} + +ecs_entity_t ecs_type_to_entity( + const ecs_world_t *world, + ecs_type_t type) +{ + return ecs_type_to_id(world, type); +} + +bool ecs_type_has_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity) +{ + return ecs_type_has_id(world, type, entity, false); +} + +bool ecs_type_owns_entity( + const ecs_world_t *world, + ecs_type_t type, + ecs_entity_t entity, + bool owned) +{ + return ecs_type_has_id(world, type, entity, owned); +} + +ecs_type_t ecs_column_type( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->types != NULL, ECS_INTERNAL_ERROR, NULL); + return it->types[index - 1]; +} + +int32_t ecs_column_index_from_name( + const ecs_iter_t *it, + const char *name) +{ + if (it->query) { + ecs_term_t *terms = it->query->filter.terms; + int32_t i, count = it->query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->name) { + if (!strcmp(name, term->name)) { + return i + 1; + } + } + } + } + + return 0; +} + +void* ecs_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column) +{ + return ecs_term_w_size(it, size, column); +} + +bool ecs_is_owned( + const ecs_iter_t *it, + int32_t column) +{ + return ecs_term_is_owned(it, column); +} + +bool ecs_is_readonly( + const ecs_iter_t *it, + int32_t column) +{ + return ecs_term_is_readonly(it, column); +} + +ecs_entity_t ecs_column_source( + const ecs_iter_t *it, + int32_t index) +{ + return ecs_term_source(it, index); +} + +ecs_id_t ecs_column_entity( + const ecs_iter_t *it, + int32_t index) +{ + return ecs_term_id(it, index); +} + +size_t ecs_column_size( + const ecs_iter_t *it, + int32_t index) +{ + return ecs_term_size(it, index); +} + +int32_t ecs_table_component_index( + const ecs_iter_t *it, + ecs_entity_t component) +{ + return ecs_iter_find_column(it, component); +} + +void* ecs_table_column( + const ecs_iter_t *it, + int32_t column_index) +{ + return ecs_iter_column_w_size(it, 0, column_index); +} + +size_t ecs_table_column_size( + const ecs_iter_t *it, + int32_t column_index) +{ + return ecs_iter_column_size(it, column_index); +} + +ecs_query_t* ecs_query_new( + ecs_world_t *world, + const char *expr) +{ + return ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = expr + }); +} + +void ecs_query_free( + ecs_query_t *query) +{ + ecs_query_fini(query); +} + +ecs_query_t* ecs_subquery_new( + ecs_world_t *world, + ecs_query_t *parent, + const char *expr) +{ + return ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = expr, + .parent = parent + }); +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/direct_access.c b/fggl/ecs2/flecs/src/addons/direct_access.c new file mode 100644 index 0000000000000000000000000000000000000000..cecc05b0587e7fd6b33dc3e7a91c72cb6cd7e20b --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/direct_access.c @@ -0,0 +1,381 @@ +#include "flecs.h" + +#ifdef FLECS_DIRECT_ACCESS + +#include "../private_api.h" + +/* Prefix with "da" so that they don't conflict with other get_column's */ + +static +ecs_column_t *da_get_column( + const ecs_table_t *table, + int32_t column) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(column <= table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_data_t *data = table->data; + if (data && data->columns) { + return &table->data->columns[column]; + } else { + return NULL; + } +} + +static +ecs_column_t *da_get_or_create_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column) +{ + ecs_column_t *c = da_get_column(table, column); + if (!c && (!table->data || !table->data->columns)) { + ecs_data_t *data = flecs_table_get_or_create_data(table); + flecs_init_data(world, table, data); + c = da_get_column(table, column); + } + ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); + return c; +} + +static +ecs_entity_t* get_entity_array( + ecs_table_t *table, + int32_t row) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data->entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *array = ecs_vector_first(table->data->entities, ecs_entity_t); + return &array[row]; +} + +/* -- Public API -- */ + +ecs_type_t ecs_table_get_type( + const ecs_table_t *table) +{ + return table->type; +} + +ecs_record_t* ecs_record_find( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_record_t *r = ecs_eis_get(world, entity); + if (r) { + return r; + } else { + return NULL; + } +} + +ecs_record_t* ecs_record_ensure( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_record_t *r = ecs_eis_ensure(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + return r; +} + +ecs_record_t ecs_table_insert( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + ecs_record_t *record) +{ + ecs_data_t *data = flecs_table_get_or_create_data(table); + int32_t index = flecs_table_append(world, table, data, entity, record, true); + if (record) { + record->table = table; + record->row = index + 1; + } + return (ecs_record_t){table, index + 1}; +} + +int32_t ecs_table_find_column( + const ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL); + return ecs_type_index_of(table->type, 0, component); +} + +ecs_vector_t* ecs_table_get_column( + const ecs_table_t *table, + int32_t column) +{ + ecs_column_t *c = da_get_column(table, column); + return c ? c->data : NULL; +} + +ecs_vector_t* ecs_table_set_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t* vector) +{ + ecs_column_t *c = da_get_or_create_column(world, table, column); + if (vector) { + ecs_vector_assert_size(vector, c->size); + } else { + ecs_vector_t *entities = ecs_table_get_entities(table); + if (entities) { + int32_t count = ecs_vector_count(entities); + vector = ecs_table_get_column(table, column); + if (!vector) { + vector = ecs_vector_new_t(c->size, c->alignment, count); + } else { + ecs_vector_set_count_t(&vector, c->size, c->alignment, count); + } + } + } + c->data = vector; + + return vector; +} + +ecs_vector_t* ecs_table_get_entities( + const ecs_table_t *table) +{ + ecs_data_t *data = table->data; + if (!data) { + return NULL; + } + + return data->entities; +} + +ecs_vector_t* ecs_table_get_records( + const ecs_table_t *table) +{ + ecs_data_t *data = table->data; + if (!data) { + return NULL; + } + + return data->record_ptrs; +} + +void ecs_table_set_entities( + ecs_table_t *table, + ecs_vector_t *entities, + ecs_vector_t *records) +{ + ecs_vector_assert_size(entities, sizeof(ecs_entity_t)); + ecs_vector_assert_size(records, sizeof(ecs_record_t*)); + ecs_assert(ecs_vector_count(entities) == ecs_vector_count(records), + ECS_INVALID_PARAMETER, NULL); + + ecs_data_t *data = table->data; + if (!data) { + data = flecs_table_get_or_create_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + } + + data->entities = entities; + data->record_ptrs = records; +} + +void ecs_records_clear( + ecs_vector_t *records) +{ + int32_t i, count = ecs_vector_count(records); + ecs_record_t **r = ecs_vector_first(records, ecs_record_t*); + + for (i = 0; i < count; i ++) { + r[i]->table = NULL; + if (r[i]->row < 0) { + r[i]->row = -1; + } else { + r[i]->row = 0; + } + } +} + +void ecs_records_update( + ecs_world_t *world, + ecs_vector_t *entities, + ecs_vector_t *records, + ecs_table_t *table) +{ + int32_t i, count = ecs_vector_count(records); + ecs_entity_t *e = ecs_vector_first(entities, ecs_entity_t); + ecs_record_t **r = ecs_vector_first(records, ecs_record_t*); + + for (i = 0; i < count; i ++) { + r[i] = ecs_record_ensure(world, e[i]); + ecs_assert(r[i] != NULL, ECS_INTERNAL_ERROR, NULL); + + r[i]->table = table; + r[i]->row = i + 1; + } +} + +void ecs_table_delete_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column, + ecs_vector_t *vector) +{ + if (!vector) { + vector = ecs_table_get_column(table, column); + if (!vector) { + return; + } + + ecs_assert(table->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + table->data->columns[column].data = NULL; + } + + ecs_column_t *c = da_get_or_create_column(world, table, column); + ecs_vector_assert_size(vector, c->size); + + ecs_type_info_t *c_info = table->c_info[column]; + ecs_xtor_t dtor; + if (c_info && (dtor = c_info->lifecycle.dtor)) { + ecs_entity_t *entities = get_entity_array(table, 0); + int16_t alignment = c->alignment; + int32_t count = ecs_vector_count(vector); + void *ptr = ecs_vector_first_t(vector, c->size, alignment); + dtor(world, c_info->component, entities, ptr, flecs_to_size_t(c->size), + count, c_info->lifecycle.ctx); + } + + if (c->data == vector) { + c->data = NULL; + } + + ecs_vector_free(vector); +} + +void* ecs_record_get_column( + ecs_record_t *r, + int32_t column, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_column(table, column); + if (!c) { + return NULL; + } + + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + void *array = ecs_vector_first_t(c->data, c->size, c->alignment); + bool is_watched; + int32_t row = flecs_record_to_row(r->row, &is_watched); + return ECS_OFFSET(array, size * row); +} + +void ecs_record_copy_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + const void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_type_info_t *c_info = table->c_info[column]; + ecs_copy_t copy; + if (c_info && (copy = c_info->lifecycle.copy)) { + ecs_entity_t *entities = get_entity_array(table, row); + copy(world, c_info->component, entities, entities, ptr, value, c_size, + count, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, value, size * count); + } +} + +void ecs_record_copy_pod_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + const void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + (void)c_size; + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_os_memcpy(ptr, value, size * count); +} + +void ecs_record_move_to( + ecs_world_t *world, + ecs_record_t *r, + int32_t column, + size_t c_size, + void *value, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(c_size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(count != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = r->table; + ecs_column_t *c = da_get_or_create_column(world, table, column); + int16_t size = c->size; + ecs_assert(!flecs_from_size_t(c_size) || flecs_from_size_t(c_size) == c->size, + ECS_INVALID_PARAMETER, NULL); + + int16_t alignment = c->alignment; + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + void *ptr = ecs_vector_get_t(c->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_type_info_t *c_info = table->c_info[column]; + ecs_move_t move; + if (c_info && (move = c_info->lifecycle.move)) { + ecs_entity_t *entities = get_entity_array(table, row); + move(world, c_info->component, entities, entities, ptr, value, c_size, + count, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, value, size * count); + } +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/module.c b/fggl/ecs2/flecs/src/addons/module.c new file mode 100644 index 0000000000000000000000000000000000000000..657107322401fa10b16429bb8bf4b0443f4cd76e --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/module.c @@ -0,0 +1,234 @@ +#include "flecs.h" + +#ifdef FLECS_MODULE + +#include "../private_api.h" + +char* ecs_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 = flflecs_to_i8(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 init_action, + const char *module_name, + void *handles_out, + size_t handles_size) +{ + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->name_prefix; + + char *path = ecs_module_path_from_c(module_name); + ecs_entity_t e = ecs_lookup_fullpath(world, path); + ecs_os_free(path); + + if (!e) { + ecs_trace_1("import %s", module_name); + ecs_log_push(); + + /* Load module */ + init_action(world); + + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup_fullpath(world, module_name); + ecs_assert(e != 0, ECS_MODULE_UNDEFINED, module_name); + + ecs_log_pop(); + } + + /* Copy value of module component in handles_out parameter */ + if (handles_size && handles_out) { + void *handles_ptr = ecs_get_mut_id(world, e, e, NULL); + ecs_os_memcpy(handles_out, handles_ptr, flecs_from_size_t(handles_size)); + } + + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->name_prefix = old_name_prefix; + + return e; +} + +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) +{ + ecs_assert(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + + char *import_func = (char*)module_name; /* safe */ + char *module = (char*)module_name; + + if (!ecs_os_has_modules() || !ecs_os_has_dl()) { + ecs_os_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 = flflecs_to_i8(toupper(ch)); + bptr ++; + capitalize = false; + } else { + *bptr = flflecs_to_i8(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_os_err("failed to find library file for '%s'", library_name); + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace_1("found file '%s' for library '%s'", + library_filename, library_name); + } + + ecs_os_dl_t dl = ecs_os_dlopen(library_filename); + if (!dl) { + ecs_os_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_1("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_os_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_1("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, NULL, 0); + + 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; +} + +void ecs_add_module_tag( + ecs_world_t *world, + ecs_entity_t module) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(module != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t e = module; + do { + ecs_add_id(world, e, EcsModule); + ecs_type_t type = ecs_get_type(world, e); + int32_t index = ecs_type_index_of(type, 0, + ecs_pair(EcsChildOf, EcsWildcard)); + if (index == -1) { + return; + } + + ecs_entity_t *pair = ecs_vector_get(type, ecs_id_t, index); + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); + e = ecs_pair_object(world, *pair); + } while (true); +} + +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + const char *name = desc->entity.name; + + char *module_path = ecs_module_path_from_c(name); + ecs_entity_t e = ecs_new_from_fullpath(world, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); + + ecs_component_desc_t private_desc = *desc; + private_desc.entity.entity = e; + private_desc.entity.name = NULL; + + 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); + + /* Add module tag */ + ecs_add_module_tag(world, result); + + /* Add module to itself. This way we have all the module information stored + * in a single contained entity that we can use for namespacing */ + ecs_set_id(world, result, result, desc->size, NULL); + + return result; +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/parser.c b/fggl/ecs2/flecs/src/addons/parser.c new file mode 100644 index 0000000000000000000000000000000000000000..4d7c09e770ef0e7dc69561acd03d5bdfeb80bc6e --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/parser.c @@ -0,0 +1,986 @@ +#include "flecs.h" + +#ifdef FLECS_PARSER + +#include "../private_api.h" + +/* TODO: after new query parser is working & code is ported to new types for + * storing terms, this code needs a big cleanup */ + +#define ECS_ANNOTATION_LENGTH_MAX (16) + +#define TOK_COLON ':' +#define TOK_AND ',' +#define TOK_OR "||" +#define TOK_NOT '!' +#define TOK_OPTIONAL '?' +#define TOK_BITWISE_OR '|' +#define TOK_NAME_SEP '.' +#define TOK_BRACKET_OPEN '[' +#define TOK_BRACKET_CLOSE ']' +#define TOK_WILDCARD '*' +#define TOK_SINGLETON '$' +#define TOK_PAREN_OPEN '(' +#define TOK_PAREN_CLOSE ')' +#define TOK_AS_ENTITY '\\' + +#define TOK_SELF "self" +#define TOK_SUPERSET "superset" +#define TOK_SUBSET "subset" +#define TOK_CASCADE_SET "cascade" +#define TOK_ALL "all" + +#define TOK_ANY "ANY" +#define TOK_OWNED "OWNED" +#define TOK_SHARED "SHARED" +#define TOK_SYSTEM "SYSTEM" +#define TOK_PARENT "PARENT" +#define TOK_CASCADE "CASCADE" + +#define TOK_ROLE_PAIR "PAIR" +#define TOK_ROLE_AND "AND" +#define TOK_ROLE_OR "OR" +#define TOK_ROLE_XOR "XOR" +#define TOK_ROLE_NOT "NOT" +#define TOK_ROLE_SWITCH "SWITCH" +#define TOK_ROLE_CASE "CASE" +#define TOK_ROLE_DISABLED "DISABLED" + +#define TOK_IN "in" +#define TOK_OUT "out" +#define TOK_INOUT "inout" + +#define ECS_MAX_TOKEN_SIZE (256) + +typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; + +static +const char *skip_newline_and_space( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +/** Skip spaces when parsing signature */ +static +const char *skip_space( + const char *ptr) +{ + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +/* -- Private functions -- */ + +static +bool valid_token_start_char( + char ch) +{ + if (ch && (isalpha(ch) || (ch == '.') || (ch == '_') || (ch == '*') || + (ch == '0') || (ch == TOK_AS_ENTITY) || isdigit(ch))) + { + return true; + } + + return false; +} + +static +bool valid_token_char( + char ch) +{ + if (ch && (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.')) { + return true; + } + + return false; +} + +static +bool valid_operator_char( + char ch) +{ + if (ch == TOK_OPTIONAL || ch == TOK_NOT) { + return true; + } + + return false; +} + +static +const char* parse_digit( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + char *token_out) +{ + ptr = skip_space(ptr); + char *tptr = token_out, ch = ptr[0]; + + if (!isdigit(ch)) { + ecs_parser_error(name, sig, column, + "invalid start of number '%s'", ptr); + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + return skip_space(ptr); +} + + +static +const char* parse_token( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + char *token_out) +{ + ptr = skip_space(ptr); + char *tptr = token_out, ch = ptr[0]; + + if (!valid_token_start_char(ch)) { + ecs_parser_error(name, sig, column, + "invalid start of identifier '%s'", ptr); + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + int tmpl_nesting = 0; + + for (; (ch = *ptr); ptr ++) { + if (ch == '<') { + tmpl_nesting ++; + } else if (ch == '>') { + if (!tmpl_nesting) { + break; + } + tmpl_nesting --; + } else + if (!valid_token_char(ch)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + if (tmpl_nesting != 0) { + ecs_parser_error(name, sig, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; + } + + return skip_space(ptr); +} + +static +int parse_identifier( + const char *token, + ecs_term_id_t *out) +{ + char ch = token[0]; + + const char *tptr = token; + if (ch == TOK_AS_ENTITY) { + tptr ++; + } + + out->name = ecs_os_strdup(tptr); + + if (ch == TOK_AS_ENTITY) { + out->var = EcsVarIsEntity; + } else if (ecs_identifier_is_var(tptr)) { + out->var = EcsVarIsVariable; + } + + return 0; +} + +static +ecs_entity_t parse_role( + const char *name, + const char *sig, + int64_t column, + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_ROLE_PAIR)) + { + return ECS_PAIR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { + return ECS_AND; + } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { + return ECS_OR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_XOR)) { + return ECS_XOR; + } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { + return ECS_NOT; + } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { + return ECS_SWITCH; + } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { + return ECS_CASE; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + return ECS_OWNED; + } else if (!ecs_os_strcmp(token, TOK_ROLE_DISABLED)) { + return ECS_DISABLED; + } else { + ecs_parser_error(name, sig, column, "invalid role '%s'", token); + return 0; + } +} + +static +ecs_oper_kind_t parse_operator( + char ch) +{ + if (ch == TOK_OPTIONAL) { + return EcsOptional; + } else if (ch == TOK_NOT) { + return EcsNot; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +static +const char* parse_annotation( + const char *name, + const char *sig, + int64_t column, + const char *ptr, + ecs_inout_kind_t *inout_kind_out) +{ + char token[ECS_MAX_TOKEN_SIZE]; + + ptr = parse_token(name, sig, column, ptr, token); + if (!ptr) { + return NULL; + } + + if (!strcmp(token, "in")) { + *inout_kind_out = EcsIn; + } else + if (!strcmp(token, "out")) { + *inout_kind_out = EcsOut; + } else + if (!strcmp(token, "inout")) { + *inout_kind_out = EcsInOut; + } + + ptr = skip_space(ptr); + + if (ptr[0] != TOK_BRACKET_CLOSE) { + ecs_parser_error(name, sig, column, "expected ]"); + return NULL; + } + + return ptr + 1; +} + +static +uint8_t parse_set_token( + const char *token) +{ + if (!ecs_os_strcmp(token, TOK_SELF)) { + return EcsSelf; + } else if (!ecs_os_strcmp(token, TOK_SUPERSET)) { + return EcsSuperSet; + } else if (!ecs_os_strcmp(token, TOK_SUBSET)) { + return EcsSubSet; + } else if (!ecs_os_strcmp(token, TOK_CASCADE_SET)) { + return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_ALL)) { + return EcsAll; + } else { + return 0; + } +} + +static +const char* parse_set_expr( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_id_t *id) +{ + do { + uint8_t tok = parse_set_token(token); + if (!tok) { + ecs_parser_error(name, expr, column, + "invalid set token '%s'", token); + return NULL; + } + + if (id->set.mask & tok) { + ecs_parser_error(name, expr, column, + "duplicate set token '%s'", token); + return NULL; + } + + if ((tok == EcsSubSet && id->set.mask & EcsSuperSet) || + (tok == EcsSuperSet && id->set.mask & EcsSubSet)) + { + ecs_parser_error(name, expr, column, + "cannot mix superset and subset", token); + return NULL; + } + + id->set.mask |= tok; + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + + /* Relationship (overrides IsA default) */ + if (!isdigit(ptr[0]) && valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + ecs_entity_t rel = ecs_lookup_fullpath(world, token); + if (!rel) { + ecs_parser_error(name, expr, column, + "unresolved identifier '%s'", token); + return NULL; + } + + id->set.relation = rel; + + if (ptr[0] == TOK_AND) { + ptr = skip_space(ptr + 1); + } else if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, + "expected ',' or ')'"); + return NULL; + } + } + + /* Max depth of search */ + if (isdigit(ptr[0])) { + ptr = parse_digit(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + id->set.max_depth = atoi(token); + if (id->set.max_depth < 0) { + ecs_parser_error(name, expr, column, + "invalid negative depth"); + return NULL; + } + + if (ptr[0] == ',') { + ptr = skip_space(ptr + 1); + } + } + + /* If another digit is found, previous depth was min depth */ + if (isdigit(ptr[0])) { + ptr = parse_digit(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + id->set.min_depth = id->set.max_depth; + id->set.max_depth = atoi(token); + if (id->set.max_depth < 0) { + ecs_parser_error(name, expr, column, + "invalid negative depth"); + return NULL; + } + } + + if (ptr[0] != TOK_PAREN_CLOSE) { + ecs_parser_error(name, expr, column, "expected ')'"); + return NULL; + } else { + ptr = skip_space(ptr + 1); + if (ptr[0] != TOK_PAREN_CLOSE && ptr[0] != TOK_AND) { + ecs_parser_error(name, expr, column, + "expected end of set expr"); + return NULL; + } + } + } + + /* Next token in set expression */ + if (ptr[0] == TOK_BITWISE_OR) { + ptr ++; + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + } + + /* End of set expression */ + } else if (ptr[0] == TOK_PAREN_CLOSE || ptr[0] == TOK_AND) { + break; + } + } while (true); + + if (id->set.mask & EcsCascade && !(id->set.mask & EcsSuperSet) && + !(id->set.mask & EcsSubSet)) + { + /* If cascade is used without specifying superset or subset, assume + * superset */ + id->set.mask |= EcsSuperSet; + } + + if (id->set.mask & EcsSelf && id->set.min_depth != 0) { + ecs_parser_error(name, expr, column, + "min_depth must be zero for set expression with 'self'"); + return NULL; + } + + return ptr; +} + +static +const char* parse_arguments( + const ecs_world_t *world, + const char *name, + const char *expr, + int64_t column, + const char *ptr, + char *token, + ecs_term_t *term) +{ + (void)column; + + int32_t arg = 0; + + do { + if (valid_token_start_char(ptr[0])) { + if (arg == 2) { + ecs_parser_error(name, expr, (ptr - expr), + "too many arguments in term"); + return NULL; + } + + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* If token is a self, superset or subset token, this is a set + * expression */ + if (!ecs_os_strcmp(token, TOK_ALL) || + !ecs_os_strcmp(token, TOK_CASCADE_SET) || + !ecs_os_strcmp(token, TOK_SELF) || + !ecs_os_strcmp(token, TOK_SUPERSET) || + !ecs_os_strcmp(token, TOK_SUBSET)) + { + ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, + token, &term->args[arg]); + if (!ptr) { + return NULL; + } + + /* Regular identifier */ + } else if (parse_identifier(token, &term->args[arg])) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + if (ptr[0] == TOK_AND) { + ptr = skip_space(ptr + 1); + + } else if (ptr[0] == TOK_PAREN_CLOSE) { + ptr = skip_space(ptr + 1); + break; + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected ',' or ')'"); + return NULL; + } + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier or set expression"); + return NULL; + } + + arg ++; + + } while (true); + + return ptr; +} + +static +const char* parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term_out) +{ + const char *ptr = expr; + char token[ECS_MAX_TOKEN_SIZE] = {0}; + ecs_term_t term = { .move = true /* parser never owns resources */ }; + + ptr = skip_space(ptr); + + /* Inout specifiers always come first */ + if (ptr[0] == TOK_BRACKET_OPEN) { + ptr = parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); + if (!ptr) { + return NULL; + } + ptr = skip_space(ptr); + } + + if (valid_operator_char(ptr[0])) { + term.oper = parse_operator(ptr[0]); + ptr = skip_space(ptr + 1); + } + + /* If next token is the start of an identifier, it could be either a type + * role, source or component identifier */ + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is token a source identifier? */ + if (ptr[0] == TOK_COLON) { + ptr ++; + goto parse_source; + } + + /* Is token a type role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr ++; + goto parse_role; + } + + /* Is token a predicate? */ + if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_predicate; + } + + /* Next token must be a predicate */ + goto parse_predicate; + + /* If next token is the source token, this is an empty source */ + } else if (ptr[0] == TOK_COLON) { + goto empty_source; + + /* If next token is a singleton, assign identifier to pred and subject */ + } else if (ptr[0] == TOK_SINGLETON) { + ptr ++; + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_singleton; + + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after singleton operator"); + return NULL; + } + + /* Pair with implicit subject */ + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + + /* Nothing else expected here */ + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ptr[0]); + return NULL; + } + +empty_source: + term.args[0].set.mask = EcsNothing; + ptr = skip_space(ptr + 1); + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_predicate; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after source operator"); + return NULL; + } + +parse_source: + if (!ecs_os_strcmp(token, TOK_PARENT)) { + term.args[0].set.mask = EcsSuperSet; + term.args[0].set.relation = EcsChildOf; + term.args[0].set.max_depth = 1; + } else if (!ecs_os_strcmp(token, TOK_SYSTEM)) { + term.args[0].name = ecs_os_strdup(name); + } else if (!ecs_os_strcmp(token, TOK_ANY)) { + term.args[0].set.mask = EcsSelf | EcsSuperSet; + term.args[0].set.relation = EcsIsA; + term.args[0].entity = EcsThis; + } else if (!ecs_os_strcmp(token, TOK_OWNED)) { + term.args[0].set.mask = EcsSelf; + term.args[0].entity = EcsThis; + } else if (!ecs_os_strcmp(token, TOK_SHARED)) { + term.args[0].set.mask = EcsSuperSet; + term.args[0].set.relation = EcsIsA; + term.args[0].entity = EcsThis; + } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { + term.args[0].set.mask = EcsSuperSet | EcsCascade; + term.args[0].set.relation = EcsChildOf; + term.args[0].entity = EcsThis; + term.oper = EcsOptional; + } else { + if (parse_identifier(token, &term.args[0])) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + } + + ptr = skip_space(ptr); + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* Is the next token a role? */ + if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { + ptr++; + goto parse_role; + } + + /* If not, it's a predicate */ + goto parse_predicate; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after source"); + return NULL; + } + +parse_role: + term.role = parse_role(name, expr, (ptr - expr), token); + if (!term.role) { + return NULL; + } + + ptr = skip_space(ptr); + + /* If next token is the source token, this is an empty source */ + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + /* If not, it's a predicate */ + goto parse_predicate; + + } else if (ptr[0] == TOK_PAREN_OPEN) { + goto parse_pair; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "expected identifier after role"); + return NULL; + } + +parse_predicate: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = skip_space(ptr); + + if (ptr[0] == TOK_PAREN_OPEN) { + ptr ++; + if (ptr[0] == TOK_PAREN_CLOSE) { + term.args[0].set.mask = EcsNothing; + ptr ++; + ptr = skip_space(ptr); + } else { + ptr = parse_arguments( + world, name, expr, (ptr - expr), ptr, token, &term); + } + + goto parse_done; + + } else if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + goto parse_name; + } + + goto parse_done; + +parse_pair: + ptr = parse_token(name, expr, (ptr - expr), ptr + 1, token); + if (!ptr) { + return NULL; + } + + if (ptr[0] == TOK_AND) { + ptr ++; + term.args[0].entity = EcsThis; + goto parse_pair_predicate; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ptr[0]); + } + +parse_pair_predicate: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = skip_space(ptr); + if (valid_token_start_char(ptr[0])) { + ptr = parse_token(name, expr, (ptr - expr), ptr, token); + if (!ptr) { + return NULL; + } + + if (ptr[0] == TOK_PAREN_CLOSE) { + ptr ++; + goto parse_pair_object; + } else { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected character '%c'", ptr[0]); + return NULL; + } + } else if (ptr[0] == TOK_PAREN_CLOSE) { + /* No object */ + goto parse_done; + } + +parse_pair_object: + if (parse_identifier(token, &term.args[1])) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + ptr = skip_space(ptr); + goto parse_done; + +parse_singleton: + if (parse_identifier(token, &term.pred)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid identifier '%s'", token); + return NULL; + } + + parse_identifier(token, &term.args[0]); + goto parse_done; + +parse_name: + term.name = ecs_os_strdup(token); + ptr = skip_space(ptr); + +parse_done: + *term_out = term; + + return ptr; +} + +char* ecs_parse_term( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_term_t *term) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_term_id_t *subj = &term->args[0]; + + bool prev_or = false; + if (ptr != expr) { + /* If this is not the start of the expression, scan back to check if + * previous token was an OR */ + const char *bptr = ptr - 1; + do { + char ch = bptr[0]; + if (isspace(ch)) { + bptr --; + continue; + } + + /* Previous token was not an OR */ + if (ch == TOK_AND) { + break; + } + + /* Previous token was an OR */ + if (ch == TOK_OR[0]) { + prev_or = true; + break; + } + + ecs_parser_error(name, expr, (ptr - expr), + "invalid preceding token"); + return NULL; + } while (true); + } + + ptr = skip_newline_and_space(ptr); + if (!ptr[0]) { + return (char*)ptr; + } + + if (ptr == expr && !strcmp(expr, "0")) { + return (char*)&ptr[1]; + } + + int32_t prev_set = subj->set.mask; + + /* Parse next element */ + ptr = parse_term(world, name, ptr, term); + if (!ptr) { + ecs_term_fini(term); + return NULL; + } + + /* Post-parse consistency checks */ + + /* If next token is OR, term is part of an OR expression */ + if (!ecs_os_strncmp(ptr, TOK_OR, 2) || prev_or) { + /* An OR operator must always follow an AND or another OR */ + if (term->oper != EcsAnd) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine || with other operators"); + ecs_term_fini(term); + return NULL; + } + + term->oper = EcsOr; + } + + /* Term must either end in end of expression, AND or OR token */ + if (ptr[0] != TOK_AND && (ptr[0] != TOK_OR[0]) && (ptr[0] != '\n') && ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "expected end of expression or next term"); + ecs_term_fini(term); + return NULL; + } + + /* If the term just contained a 0, the expression has nothing. Ensure + * that after the 0 nothing else follows */ + if (!ecs_os_strcmp(term->pred.name, "0")) { + if (ptr[0]) { + ecs_parser_error(name, expr, (ptr - expr), + "unexpected term after 0"); + ecs_term_fini(term); + return NULL; + } + + subj->set.mask = EcsNothing; + } + + /* Cannot combine EcsNothing with operators other than AND */ + if (term->oper != EcsAnd && subj->set.mask == EcsNothing) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid operator for empty source"); + ecs_term_fini(term); + return NULL; + } + + /* Verify consistency of OR expression */ + if (prev_or && term->oper == EcsOr) { + /* Set expressions must be the same for all OR terms */ + if (subj->set.mask != prev_set) { + ecs_parser_error(name, expr, (ptr - expr), + "cannot combine different sources in OR expression"); + ecs_term_fini(term); + return NULL; + } + + term->oper = EcsOr; + } + + /* Automatically assign This if entity is not assigned and the set is + * nothing */ + if (subj->set.mask != EcsNothing) { + if (!subj->name) { + if (!subj->entity) { + subj->entity = EcsThis; + } + } + } + + if (subj->name && !ecs_os_strcmp(subj->name, "0")) { + subj->entity = 0; + subj->set.mask = EcsNothing; + } + + /* Process role */ + if (term->role == ECS_AND) { + term->oper = EcsAndFrom; + term->role = 0; + } else if (term->role == ECS_OR) { + term->oper = EcsOrFrom; + term->role = 0; + } else if (term->role == ECS_NOT) { + term->oper = EcsNotFrom; + term->role = 0; + } + + if (ptr[0]) { + if (ptr[0] == ',') { + ptr ++; + } else if (ptr[0] == '|') { + ptr += 2; + } + } + + ptr = skip_space(ptr); + + return (char*)ptr; +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/plecs.c b/fggl/ecs2/flecs/src/addons/plecs.c new file mode 100644 index 0000000000000000000000000000000000000000..739a5c5a952e9a637f189b2b4ce59fe54e5237bf --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/plecs.c @@ -0,0 +1,136 @@ +#include "flecs.h" + +#ifdef FLECS_PLECS + +#include "../private_api.h" + +#define TOK_NEWLINE '\n' + +static +ecs_entity_t ensure_entity( + ecs_world_t *world, + const char *path) +{ + ecs_entity_t e = ecs_lookup_fullpath(world, path); + if (!e) { + e = ecs_new_from_path(world, 0, path); + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + } + + return e; +} + +static +int create_term( + ecs_world_t *world, + ecs_term_t *term, + const char *name, + const char *expr, + int32_t column) +{ + if (!ecs_term_id_is_set(&term->pred)) { + ecs_parser_error(name, expr, column, "missing predicate in expression"); + return -1; + } + + if (!ecs_term_id_is_set(&term->args[0])) { + ecs_parser_error(name, expr, column, "missing subject in expression"); + return -1; + } + + ecs_entity_t pred = ensure_entity(world, term->pred.name); + ecs_entity_t subj = ensure_entity(world, term->args[0].name); + ecs_entity_t obj = 0; + + if (ecs_term_id_is_set(&term->args[1])) { + obj = ensure_entity(world, term->args[1].name); + } + + if (!obj) { + ecs_add_id(world, subj, pred); + } else { + ecs_add_pair(world, subj, pred, obj); + } + + return 0; +} + +int ecs_plecs_from_str( + ecs_world_t *world, + const char *name, + const char *expr) +{ + const char *ptr = expr; + ecs_term_t term = {0}; + + if (!expr) { + return 0; + } + + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (create_term(world, &term, name, expr, (int32_t)(ptr - expr))) { + return -1; + } + + ecs_term_fini(&term); + + if (ptr[0] == TOK_NEWLINE) { + ptr ++; + expr = ptr; + } + } + + return 0; +} + +int ecs_plecs_from_file( + ecs_world_t *world, + 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; + } + rewind(file); + + /* 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); + + int result = ecs_plecs_from_str(world, filename, content); + ecs_os_free(content); + return result; +error: + ecs_os_free(content); + return -1; +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/queue.c b/fggl/ecs2/flecs/src/addons/queue.c new file mode 100644 index 0000000000000000000000000000000000000000..dd4e04e0328da2618a225d277eedeb84e604fc6b --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/queue.c @@ -0,0 +1,101 @@ +#include "flecs.h" + +#ifdef FLECS_QUEUE + +struct ecs_queue_t { + ecs_vector_t *data; + int32_t index; +}; + +ecs_queue_t* _ecs_queue_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->data = _ecs_vector_new(elem_size, offset, elem_count); + result->index = 0; + return result; +} + +ecs_queue_t* _ecs_queue_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_queue_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_queue_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->data = _ecs_vector_from_array(elem_size, offset, elem_count, array); + result->index = 0; + return result; +} + +void* _ecs_queue_push( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset) +{ + int32_t size = ecs_vector_size(buffer->data); + int32_t count = ecs_vector_count(buffer->data); + void *result; + + if (count == buffer->index) { + result = _ecs_vector_add(&buffer->data, elem_size, offset); + } else { + result = _ecs_vector_get(buffer->data, elem_size, offset, buffer->index); + } + + buffer->index = (buffer->index + 1) % size; + + return result; +} + +void ecs_queue_free( + ecs_queue_t *buffer) +{ + ecs_vector_free(buffer->data); + ecs_os_free(buffer); +} + +void* _ecs_queue_get( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + int32_t count = ecs_vector_count(buffer->data); + int32_t size = ecs_vector_size(buffer->data); + index = ((buffer->index - count + size) + (int32_t)index) % size; + return _ecs_vector_get(buffer->data, elem_size, offset, index); +} + +void* _ecs_queue_last( + ecs_queue_t *buffer, + ecs_size_t elem_size, + int16_t offset) +{ + int32_t index = buffer->index; + if (!index) { + index = ecs_vector_size(buffer->data); + } + + return _ecs_vector_get(buffer->data, elem_size, offset, index - 1); +} + +int32_t ecs_queue_index( + ecs_queue_t *buffer) +{ + return buffer->index; +} + +int32_t ecs_queue_count( + ecs_queue_t *buffer) +{ + return ecs_vector_count(buffer->data); +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/snapshot.c b/fggl/ecs2/flecs/src/addons/snapshot.c new file mode 100644 index 0000000000000000000000000000000000000000..36d2b9f8584aea6187032e38ea1148376c79b469 --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/snapshot.c @@ -0,0 +1,356 @@ +#include "flecs.h" + +#ifdef FLECS_SNAPSHOT + +#include "../private_api.h" + +/* World snapshot */ +struct ecs_snapshot_t { + ecs_world_t *world; + ecs_sparse_t *entity_index; + ecs_vector_t *tables; + ecs_entity_t last_id; + ecs_filter_t filter; +}; + +static +ecs_data_t* duplicate_data( + const ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *main_data) +{ + ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + + int32_t i, column_count = table->column_count; + ecs_entity_t *components = ecs_vector_first(table->type, ecs_entity_t); + + result->columns = ecs_os_memdup( + main_data->columns, ECS_SIZEOF(ecs_column_t) * column_count); + + /* Copy entities */ + result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); + ecs_entity_t *entities = ecs_vector_first(result->entities, ecs_entity_t); + + /* Copy record ptrs */ + result->record_ptrs = ecs_vector_copy(main_data->record_ptrs, ecs_record_t*); + + /* Copy each column */ + for (i = 0; i < column_count; i ++) { + ecs_entity_t component = components[i]; + ecs_column_t *column = &result->columns[i]; + + component = ecs_get_typeid(world, component); + + const ecs_type_info_t *cdata = flecs_get_c_info(world, component); + int16_t size = column->size; + int16_t alignment = column->alignment; + ecs_copy_t copy; + + if (cdata && (copy = cdata->lifecycle.copy)) { + int32_t count = ecs_vector_count(column->data); + ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, count); + ecs_vector_set_count_t(&dst_vec, size, alignment, count); + void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); + void *ctx = cdata->lifecycle.ctx; + + ecs_xtor_t ctor = cdata->lifecycle.ctor; + if (ctor) { + ctor((ecs_world_t*)world, component, entities, dst_ptr, + flecs_to_size_t(size), count, ctx); + } + + void *src_ptr = ecs_vector_first_t(column->data, size, alignment); + copy((ecs_world_t*)world, component, entities, entities, dst_ptr, + src_ptr, flecs_to_size_t(size), count, ctx); + + column->data = dst_vec; + } else { + column->data = ecs_vector_copy_t(column->data, size, alignment); + } + } + + return result; +} + +static +ecs_snapshot_t* snapshot_create( + const ecs_world_t *world, + const ecs_sparse_t *entity_index, + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_snapshot_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_snapshot_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->world = (ecs_world_t*)world; + + /* If no iterator is provided, the snapshot will be taken of the entire + * world, and we can simply copy the entity index as it will be restored + * entirely upon snapshote restore. */ + if (!iter && entity_index) { + result->entity_index = flecs_sparse_copy(entity_index); + result->tables = ecs_vector_new(ecs_table_leaf_t, 0); + } + + ecs_iter_t iter_stack; + if (!iter) { + iter_stack = ecs_filter_iter(world, NULL); + iter = &iter_stack; + next = ecs_filter_next; + } + + /* If an iterator is provided, this is a filterred snapshot. In this case we + * have to patch the entity index one by one upon restore, as we don't want + * to affect entities that were not part of the snapshot. */ + else { + result->entity_index = NULL; + } + + /* Iterate tables in iterator */ + while (next(iter)) { + ecs_table_t *t = iter->table; + + if (t->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_data_t *data = flecs_table_get_data(t); + if (!data || !data->entities || !ecs_vector_count(data->entities)) { + continue; + } + + ecs_table_leaf_t *l = ecs_vector_add(&result->tables, ecs_table_leaf_t); + l->table = t; + l->type = t->type; + l->data = duplicate_data(world, t, data); + } + + return result; +} + +/** Create a snapshot */ +ecs_snapshot_t* ecs_snapshot_take( + ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); + + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + NULL, + NULL); + + result->last_id = world->stats.last_id; + + return result; +} + +/** Create a filtered snapshot */ +ecs_snapshot_t* ecs_snapshot_take_w_iter( + ecs_iter_t *iter, + ecs_iter_next_action_t next) +{ + ecs_world_t *world = iter->world; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_snapshot_t *result = snapshot_create( + world, + world->store.entity_index, + iter, + next); + + result->last_id = world->stats.last_id; + + return result; +} + +/** Restore a snapshot */ +void ecs_snapshot_restore( + ecs_world_t *world, + ecs_snapshot_t *snapshot) +{ + bool is_filtered = true; + + if (snapshot->entity_index) { + flecs_sparse_restore(world->store.entity_index, snapshot->entity_index); + flecs_sparse_free(snapshot->entity_index); + is_filtered = false; + } + + if (!is_filtered) { + world->stats.last_id = snapshot->last_id; + } + + ecs_table_leaf_t *leafs = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t l = 0, count = ecs_vector_count(snapshot->tables); + int32_t t, table_count = flecs_sparse_count(world->store.tables); + + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, t); + + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_table_leaf_t *leaf = NULL; + if (l < count) { + leaf = &leafs[l]; + } + + if (leaf && leaf->table == table) { + /* If the snapshot is filtered, update the entity index for the + * entities in the snapshot. If the snapshot was not filtered + * the entity index would have been replaced entirely, and this + * is not necessary. */ + if (is_filtered) { + ecs_vector_each(leaf->data->entities, ecs_entity_t, e_ptr, { + ecs_record_t *r = ecs_eis_get(world, *e_ptr); + if (r && r->table) { + ecs_data_t *data = flecs_table_get_data(r->table); + + /* Data must be not NULL, otherwise entity index could + * not point to it */ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + + /* Always delete entity, so that even if the entity is + * in the current table, there won't be duplicates */ + flecs_table_delete(world, r->table, data, row, true); + } else { + ecs_eis_set_generation(world, *e_ptr); + } + }); + + int32_t old_count = ecs_table_count(table); + int32_t new_count = flecs_table_data_count(leaf->data); + + ecs_data_t *data = flecs_table_get_data(table); + data = flecs_table_merge(world, table, table, data, leaf->data); + + /* Run OnSet systems for merged entities */ + flecs_run_set_systems(world, 0, table, data, NULL, + old_count, new_count, true); + + ecs_os_free(leaf->data->columns); + } else { + flecs_table_replace_data(world, table, leaf->data); + } + + ecs_os_free(leaf->data); + l ++; + } else { + /* If the snapshot is not filtered, the snapshot should restore the + * world to the exact state it was in. When a snapshot is filtered, + * it should only update the entities that were in the snapshot. + * If a table is found that was not in the snapshot, and the + * snapshot was not filtered, clear the table. */ + if (!is_filtered) { + /* Clear data of old table. */ + flecs_table_clear_data(world, table, flecs_table_get_data(table)); + } + } + + table->alloc_count ++; + } + + /* If snapshot was not filtered, run OnSet systems now. This cannot be done + * while restoring the snapshot, because the world is in an inconsistent + * state while restoring. When a snapshot is filtered, the world is not left + * in an inconsistent state, which makes running OnSet systems while + * restoring safe */ + if (!is_filtered) { + for (t = 0; t < table_count; t ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, t); + if (table->flags & EcsTableHasBuiltins) { + continue; + } + + ecs_data_t *table_data = flecs_table_get_data(table); + int32_t entity_count = flecs_table_data_count(table_data); + + flecs_run_set_systems(world, 0, table, + table_data, NULL, 0, entity_count, true); + } + } + + ecs_vector_free(snapshot->tables); + + ecs_os_free(snapshot); +} + +ecs_iter_t ecs_snapshot_iter( + ecs_snapshot_t *snapshot, + const ecs_filter_t *filter) +{ + ecs_snapshot_iter_t iter = { + .filter = filter ? *filter : (ecs_filter_t){0}, + .tables = snapshot->tables, + .index = 0 + }; + + return (ecs_iter_t){ + .world = snapshot->world, + .table_count = ecs_vector_count(snapshot->tables), + .iter.snapshot = iter + }; +} + +bool ecs_snapshot_next( + ecs_iter_t *it) +{ + ecs_snapshot_iter_t *iter = &it->iter.snapshot; + ecs_table_leaf_t *tables = ecs_vector_first(iter->tables, ecs_table_leaf_t); + int32_t count = ecs_vector_count(iter->tables); + int32_t i; + + for (i = iter->index; i < count; i ++) { + ecs_table_t *table = tables[i].table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = tables[i].data; + + /* Table must have data or it wouldn't have been added */ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!flecs_table_match_filter(it->world, table, &iter->filter)) { + continue; + } + + it->table = table; + it->table_columns = data->columns; + it->count = flecs_table_data_count(data); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->is_valid = true; + iter->index = i + 1; + goto yield; + } + + it->is_valid = false; + return false; + +yield: + it->is_valid = true; + return true; +} + +/** Cleanup snapshot */ +void ecs_snapshot_free( + ecs_snapshot_t *snapshot) +{ + flecs_sparse_free(snapshot->entity_index); + + ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); + int32_t i, count = ecs_vector_count(snapshot->tables); + for (i = 0; i < count; i ++) { + ecs_table_leaf_t *leaf = &tables[i]; + flecs_table_clear_data(snapshot->world, leaf->table, leaf->data); + ecs_os_free(leaf->data); + } + + ecs_vector_free(snapshot->tables); + ecs_os_free(snapshot); +} + +#endif diff --git a/fggl/ecs2/flecs/src/addons/stats.c b/fggl/ecs2/flecs/src/addons/stats.c new file mode 100644 index 0000000000000000000000000000000000000000..9da6938324ca8e82238331370e1e450cb3cce9be --- /dev/null +++ b/fggl/ecs2/flecs/src/addons/stats.c @@ -0,0 +1,385 @@ + +#include "../private_api.h" + +#ifdef FLECS_STATS + +#ifdef FLECS_SYSTEM +#include "../modules/system/system.h" +#endif + +#ifdef FLECS_PIPELINE +#include "../modules/pipeline/pipeline.h" +#endif + +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 _record_gauge( + ecs_gauge_t *m, + int32_t t, + float value) +{ + m->avg[t] = value; + m->min[t] = value; + m->max[t] = value; +} + +static +float _record_counter( + ecs_counter_t *m, + int32_t t, + float value) +{ + int32_t tp = t_prev(t); + float prev = m->value[tp]; + m->value[t] = value; + _record_gauge((ecs_gauge_t*)m, t, value - prev); + return value - prev; +} + +/* Macro's to silence conversion warnings without adding casts everywhere */ +#define record_gauge(m, t, value)\ + _record_gauge(m, t, (float)value) + +#define record_counter(m, t, value)\ + _record_counter(m, t, (float)value) + +static +void print_value( + const char *name, + float value) +{ + ecs_size_t len = ecs_os_strlen(name); + printf("%s: %*s %.2f\n", name, 32 - len, "", (double)value); +} + +static +void print_gauge( + const char *name, + int32_t t, + const ecs_gauge_t *m) +{ + print_value(name, m->avg[t]); +} + +static +void print_counter( + const char *name, + int32_t t, + const ecs_counter_t *m) +{ + print_value(name, m->rate.avg[t]); +} + +void ecs_gauge_reduce( + ecs_gauge_t *dst, + int32_t t_dst, + ecs_gauge_t *src, + int32_t t_src) +{ + ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(src != NULL, ECS_INVALID_PARAMETER, NULL); + + bool min_set = false; + dst->min[t_dst] = 0; + dst->avg[t_dst] = 0; + dst->max[t_dst] = 0; + + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; + if (!min_set || (src->min[t] < dst->min[t_dst])) { + dst->min[t_dst] = src->min[t]; + min_set = true; + } + if ((src->max[t] > dst->max[t_dst])) { + dst->max[t_dst] = src->max[t]; + } + } +} + +void ecs_get_world_stats( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + int32_t t = s->t = t_next(s->t); + + float delta_world_time = record_counter(&s->world_time_total_raw, t, world->stats.world_time_total_raw); + record_counter(&s->world_time_total, t, world->stats.world_time_total); + record_counter(&s->frame_time_total, t, world->stats.frame_time_total); + record_counter(&s->system_time_total, t, world->stats.system_time_total); + record_counter(&s->merge_time_total, t, world->stats.merge_time_total); + + float delta_frame_count = record_counter(&s->frame_count_total, t, world->stats.frame_count_total); + record_counter(&s->merge_count_total, t, world->stats.merge_count_total); + record_counter(&s->pipeline_build_count_total, t, world->stats.pipeline_build_count_total); + record_counter(&s->systems_ran_frame, t, world->stats.systems_ran_frame); + + if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { + record_gauge( + &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); + } else { + record_gauge(&s->fps, t, 0); + } + + record_gauge(&s->entity_count, t, flecs_sparse_count(world->store.entity_index)); + record_gauge(&s->component_count, t, ecs_count_id(world, ecs_id(EcsComponent))); + record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); + record_gauge(&s->system_count, t, ecs_count_id(world, ecs_id(EcsSystem))); + + record_counter(&s->new_count, t, world->new_count); + record_counter(&s->bulk_new_count, t, world->bulk_new_count); + record_counter(&s->delete_count, t, world->delete_count); + record_counter(&s->clear_count, t, world->clear_count); + record_counter(&s->add_count, t, world->add_count); + record_counter(&s->remove_count, t, world->remove_count); + record_counter(&s->set_count, t, world->set_count); + record_counter(&s->discard_count, t, world->discard_count); + + /* Compute table statistics */ + int32_t empty_table_count = 0; + int32_t singleton_table_count = 0; + int32_t matched_table_count = 0, matched_entity_count = 0; + + int32_t i, count = flecs_sparse_count(world->store.tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + int32_t entity_count = ecs_table_count(table); + + if (!entity_count) { + empty_table_count ++; + } + + /* Singleton tables are tables that have just one entity that also has + * itself in the table type. */ + if (entity_count == 1) { + ecs_data_t *data = flecs_table_get_data(table); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + if (ecs_type_has_id(world, table->type, entities[0], false)) { + singleton_table_count ++; + } + } + + /* If this table matches with queries and is not empty, increase the + * matched table & matched entity count. These statistics can be used to + * compute actual fragmentation ratio for queries. */ + int32_t queries_matched = ecs_vector_count(table->queries); + if (queries_matched && entity_count) { + matched_table_count ++; + matched_entity_count += entity_count; + } + } + + record_gauge(&s->matched_table_count, t, matched_table_count); + record_gauge(&s->matched_entity_count, t, matched_entity_count); + + record_gauge(&s->table_count, t, count); + record_gauge(&s->empty_table_count, t, empty_table_count); + record_gauge(&s->singleton_table_count, t, singleton_table_count); +} + +void ecs_get_query_stats( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + int32_t t = s->t = t_next(s->t); + + int32_t i, entity_count = 0, count = ecs_vector_count(query->tables); + ecs_matched_table_t *matched_tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + for (i = 0; i < count; i ++) { + ecs_matched_table_t *matched = &matched_tables[i]; + if (matched->table) { + entity_count += ecs_table_count(matched->table); + } + } + + record_gauge(&s->matched_table_count, t, count); + record_gauge(&s->matched_empty_table_count, t, + ecs_vector_count(query->empty_tables)); + record_gauge(&s->matched_entity_count, t, entity_count); +} + +#ifdef FLECS_SYSTEM +bool ecs_get_system_stats( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const EcsSystem *ptr = ecs_get(world, system, EcsSystem); + if (!ptr) { + return false; + } + + ecs_get_query_stats(world, ptr->query, &s->query_stats); + int32_t t = s->query_stats.t; + + record_counter(&s->time_spent, t, ptr->time_spent); + record_counter(&s->invoke_count, t, ptr->invoke_count); + record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); + record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + + return true; +} +#endif + + +#ifdef FLECS_PIPELINE + +static ecs_system_stats_t* get_system_stats( + ecs_map_t *systems, + ecs_entity_t system) +{ + ecs_assert(systems != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system); + if (!s) { + ecs_system_stats_t stats; + memset(&stats, 0, sizeof(ecs_system_stats_t)); + ecs_map_set(systems, system, &stats); + s = ecs_map_get(systems, ecs_system_stats_t, system); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + } + + return s; +} + +bool ecs_get_pipeline_stats( + const ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + if (!pq) { + return false; + } + + /* First find out how many systems are matched by the pipeline */ + ecs_iter_t it = ecs_query_iter(pq->query); + int32_t count = 0; + while (ecs_query_next(&it)) { + count += it.count; + } + + if (!s->system_stats) { + s->system_stats = ecs_map_new(ecs_system_stats_t, count); + } + + /* Also count synchronization points */ + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + count += ecs_vector_count(ops); + + /* Make sure vector is large enough to store all systems & sync points */ + ecs_vector_set_count(&s->systems, ecs_entity_t, count - 1); + ecs_entity_t *systems = ecs_vector_first(s->systems, ecs_entity_t); + + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(pq->query); + int32_t i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + int32_t i; + 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 */ + } + + ecs_system_stats_t *sys_stats = get_system_stats( + s->system_stats, it.entities[i]); + ecs_get_system_stats(world, it.entities[i], sys_stats); + } + } + + ecs_assert(i_system == (count - 1), ECS_INTERNAL_ERROR, NULL); + + return true; +} +#endif + +void ecs_dump_world_stats( + const ecs_world_t *world, + const ecs_world_stats_t *s) +{ + int32_t t = s->t; + + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(s != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + print_counter("Frame", t, &s->frame_count_total); + printf("-------------------------------------\n"); + print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); + print_counter("systems ran last frame", t, &s->systems_ran_frame); + printf("\n"); + print_value("target FPS", world->stats.target_fps); + print_value("time scale", world->stats.time_scale); + printf("\n"); + print_gauge("actual FPS", t, &s->fps); + print_counter("frame time", t, &s->frame_time_total); + print_counter("system time", t, &s->system_time_total); + print_counter("merge time", t, &s->merge_time_total); + print_counter("simulation time elapsed", t, &s->world_time_total); + printf("\n"); + print_gauge("entity count", t, &s->entity_count); + print_gauge("component count", t, &s->component_count); + print_gauge("query count", t, &s->query_count); + print_gauge("system count", t, &s->system_count); + print_gauge("table count", t, &s->table_count); + print_gauge("singleton table count", t, &s->singleton_table_count); + print_gauge("empty table count", t, &s->empty_table_count); + printf("\n"); + print_counter("deferred new operations", t, &s->new_count); + print_counter("deferred bulk_new operations", t, &s->bulk_new_count); + print_counter("deferred delete operations", t, &s->delete_count); + print_counter("deferred clear operations", t, &s->clear_count); + print_counter("deferred add operations", t, &s->add_count); + print_counter("deferred remove operations", t, &s->remove_count); + print_counter("deferred set operations", t, &s->set_count); + print_counter("discarded operations", t, &s->discard_count); + printf("\n"); +} + +#endif diff --git a/fggl/ecs2/flecs/src/api_support.c b/fggl/ecs2/flecs/src/api_support.c new file mode 100644 index 0000000000000000000000000000000000000000..27cf90886bc7cce734d4a3983b543c750db02d4f --- /dev/null +++ b/fggl/ecs2/flecs/src/api_support.c @@ -0,0 +1,213 @@ +#include "private_api.h" + +static +ecs_vector_t* sort_and_dedup( + ecs_vector_t *result) +{ + /* Sort vector */ + ecs_vector_sort(result, ecs_id_t, flecs_entity_compare_qsort); + + /* Ensure vector doesn't contain duplicates */ + ecs_id_t *ids = ecs_vector_first(result, ecs_id_t); + int32_t i, offset = 0, count = ecs_vector_count(result); + + for (i = 0; i < count; i ++) { + if (i && ids[i] == ids[i - 1]) { + offset ++; + } + + if (i + offset >= count) { + break; + } + + ids[i] = ids[i + offset]; + } + + ecs_vector_set_count(&result, ecs_id_t, i - offset); + + return result; +} + +/** Parse callback that adds type to type identifier */ +static +ecs_vector_t* expr_to_ids( + ecs_world_t *world, + const char *name, + const char *expr) +{ +#ifdef FLECS_PARSER + ecs_vector_t *result = NULL; + const char *ptr = expr; + ecs_term_t term = {0}; + + if (!ptr) { + return NULL; + } + + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (term.name) { + ecs_parser_error(name, expr, (ptr - expr), + "column names not supported in type expression"); + goto error; + } + + if (term.oper != EcsAnd && term.oper != EcsAndFrom) { + ecs_parser_error(name, expr, (ptr - expr), + "operator other than AND not supported in type expression"); + goto error; + } + + if (ecs_term_finalize(world, name, expr, &term)) { + goto error; + } + + if (term.args[0].entity == 0) { + /* Empty term */ + goto done; + } + + if (term.args[0].set.mask != EcsDefaultSet) { + ecs_parser_error(name, expr, (ptr - expr), + "source modifiers not supported for type expressions"); + goto error; + } + + if (term.args[0].entity != EcsThis) { + ecs_parser_error(name, expr, (ptr - expr), + "subject other than this not supported in type expression"); + goto error; + } + + if (term.oper == EcsAndFrom) { + term.role = ECS_AND; + } + + ecs_id_t* elem = ecs_vector_add(&result, ecs_id_t); + *elem = term.id | term.role; + + ecs_term_fini(&term); + } + + result = sort_and_dedup(result); + +done: + return result; +error: + ecs_term_fini(&term); + ecs_vector_free(result); + return NULL; +#else + (void)world; + (void)name; + (void)expr; + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + return NULL; +#endif +} + +/* Create normalized type. A normalized type resolves all elements with an + * AND flag and appends them to the resulting type, where the default type + * maintains the original type hierarchy. */ +static +ecs_vector_t* ids_to_normalized_ids( + ecs_world_t *world, + ecs_vector_t *ids) +{ + ecs_vector_t *result = NULL; + + ecs_entity_t *array = ecs_vector_first(ids, ecs_id_t); + int32_t i, count = ecs_vector_count(ids); + + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, AND)) { + ecs_entity_t entity = ECS_PAIR_OBJECT(e); + + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, ECS_INVALID_PARAMETER, + "flag must be applied to type"); + + ecs_vector_each(type_ptr->normalized, ecs_id_t, c_ptr, { + ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); + *el = *c_ptr; + }) + } else { + ecs_entity_t *el = ecs_vector_add(&result, ecs_id_t); + *el = e; + } + } + + return sort_and_dedup(result); +} + +static +ecs_table_t* table_from_ids( + ecs_world_t *world, + ecs_vector_t *ids) +{ + ecs_ids_t ids_array = flecs_type_to_ids(ids); + ecs_table_t *result = flecs_table_find_or_create(world, &ids_array); + return result; +} + +/* If a name prefix is set with ecs_set_name_prefix, check if the entity name + * has the prefix, and if so remove it. This enables using prefixed names in C + * for components / systems while storing a canonical / language independent + * identifier. */ +const char* flecs_name_from_symbol( + ecs_world_t *world, + const char *type_name) +{ + const char *prefix = world->name_prefix; + if (type_name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(type_name, prefix, len) && + (isupper(type_name[len]) || type_name[len] == '_')) + { + if (type_name[len] == '_') { + return type_name + len + 1; + } else { + return type_name + len; + } + } + } + + return type_name; +} + +/* -- Public functions -- */ + +ecs_type_t ecs_type_from_str( + ecs_world_t *world, + const char *expr) +{ + ecs_vector_t *ids = expr_to_ids(world, NULL, expr); + if (!ids) { + return NULL; + } + + ecs_vector_t *normalized_ids = ids_to_normalized_ids(world, ids); + ecs_vector_free(ids); + + ecs_table_t *table = table_from_ids(world, normalized_ids); + ecs_vector_free(normalized_ids); + + return table->type; +} + +ecs_table_t* ecs_table_from_str( + ecs_world_t *world, + const char *expr) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + ecs_vector_t *ids = expr_to_ids(world, NULL, expr); + if (!ids) { + return NULL; + } + + ecs_table_t *result = table_from_ids(world, ids); + ecs_vector_free(ids); + + return result; +} diff --git a/fggl/ecs2/flecs/src/bitset.c b/fggl/ecs2/flecs/src/bitset.c new file mode 100644 index 0000000000000000000000000000000000000000..45c4dc7848798734b1d0a80ee08ef9247fa9bc84 --- /dev/null +++ b/fggl/ecs2/flecs/src/bitset.c @@ -0,0 +1,103 @@ + +#include "private_api.h" + +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_deinit( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); +} + +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_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t hi = elem >> 6; + int32_t lo = elem & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +} + +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +} + +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_assert(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = flecs_bitset_get(bs, last); + flecs_bitset_set(bs, elem, last_value); + bs->count --; +} + +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_assert(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(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); +} diff --git a/fggl/ecs2/flecs/src/bootstrap.c b/fggl/ecs2/flecs/src/bootstrap.c new file mode 100644 index 0000000000000000000000000000000000000000..5a959d707211cc5c97f8fdda27165ff41895802c --- /dev/null +++ b/fggl/ecs2/flecs/src/bootstrap.c @@ -0,0 +1,427 @@ +#include "private_api.h" + +/* Global type variables */ +ecs_type_t ecs_type(EcsComponent); +ecs_type_t ecs_type(EcsType); +ecs_type_t ecs_type(EcsIdentifier); +ecs_type_t ecs_type(EcsQuery); +ecs_type_t ecs_type(EcsTrigger); +ecs_type_t ecs_type(EcsObserver); +ecs_type_t ecs_type(EcsPrefab); + +/* Component lifecycle actions for EcsIdentifier */ +static ECS_CTOR(EcsIdentifier, ptr, { + ptr->value = NULL; + ptr->hash = 0; + ptr->length = 0; +}) + +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; +}) + +static ECS_MOVE(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, NULL); + dst->value = src->value; + dst->hash = src->hash; + dst->length = src->length; + + src->value = NULL; + src->hash = 0; + src->length = 0; + +}) + +static ECS_ON_SET(EcsIdentifier, ptr, { + if (ptr->value) { + ptr->length = ecs_os_strlen(ptr->value); + ptr->hash = flecs_hash(ptr->value, ptr->length); + } else { + ptr->length = 0; + ptr->hash = 0; + } +}) + +/* Component lifecycle actions for EcsTrigger */ +static ECS_CTOR(EcsTrigger, ptr, { + ptr->trigger = NULL; +}) + +static ECS_DTOR(EcsTrigger, ptr, { + flecs_trigger_fini(world, (ecs_trigger_t*)ptr->trigger); +}) + +static ECS_COPY(EcsTrigger, dst, src, { + ecs_abort(ECS_INVALID_OPERATION, "Trigger component cannot be copied"); +}) + +static ECS_MOVE(EcsTrigger, dst, src, { + if (dst->trigger) { + flecs_trigger_fini(world, (ecs_trigger_t*)dst->trigger); + } + dst->trigger = src->trigger; + src->trigger = NULL; +}) + +/* Component lifecycle actions for EcsObserver */ +static ECS_CTOR(EcsObserver, ptr, { + ptr->observer = NULL; +}) + +static ECS_DTOR(EcsObserver, ptr, { + flecs_observer_fini(world, (ecs_observer_t*)ptr->observer); +}) + +static ECS_COPY(EcsObserver, dst, src, { + ecs_abort(ECS_INVALID_OPERATION, "Observer component cannot be copied"); +}) + +static ECS_MOVE(EcsObserver, dst, src, { + if (dst->observer) { + flecs_observer_fini(world, (ecs_observer_t*)dst->observer); + } + dst->observer = src->observer; + src->observer = NULL; +}) + +static +void register_on_delete(ecs_iter_t *it) { + ecs_id_t id = ecs_term_id(it, 1); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_id_record_t *r = flecs_ensure_id_record(it->world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete = ECS_PAIR_OBJECT(id); + + r = flecs_ensure_id_record(it->world, ecs_pair(e, EcsWildcard)); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete = ECS_PAIR_OBJECT(id); + + flecs_set_watch(it->world, e); + } +} + +static +void register_on_delete_object(ecs_iter_t *it) { + ecs_id_t id = ecs_term_id(it, 1); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_id_record_t *r = flecs_ensure_id_record(it->world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + r->on_delete_object = ECS_PAIR_OBJECT(id); + + flecs_set_watch(it->world, e); + } +} + +static +void on_set_component_lifecycle( ecs_iter_t *it) { + EcsComponentLifecycle *cl = ecs_term(it, EcsComponentLifecycle, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_set_component_actions_w_id(world, e, &cl[i]); + } +} + +/* -- Bootstrapping -- */ + +#define bootstrap_component(world, table, name)\ + _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) + +static +void _bootstrap_component( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = flecs_table_get_or_create_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *columns = data->columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = ecs_eis_ensure(world, entity); + record->table = table; + + int32_t index = flecs_table_append(world, table, data, entity, record, false); + record->row = index + 1; + + EcsComponent *component = ecs_vector_first(columns[0].data, EcsComponent); + 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_vector_first(columns[1].data, EcsIdentifier); + name_col[index].value = ecs_os_strdup(name); + name_col[index].length = name_length; + name_col[index].hash = flecs_hash(name, name_length); + + EcsIdentifier *symbol_col = ecs_vector_first(columns[2].data, EcsIdentifier); + symbol_col[index].value = ecs_os_strdup(symbol); + symbol_col[index].length = symbol_length; + symbol_col[index].hash = flecs_hash(symbol, symbol_length); +} + +/** Create type for component */ +ecs_type_t flecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_table_t *table = flecs_table_find_or_create(world, &(ecs_ids_t){ + .array = (ecs_entity_t[]){entity}, + .count = 1 + }); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->type != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +/** Bootstrap types for builtin components and tags */ +static +void bootstrap_types( + ecs_world_t *world) +{ + ecs_type(EcsComponent) = flecs_bootstrap_type(world, ecs_id(EcsComponent)); + ecs_type(EcsType) = flecs_bootstrap_type(world, ecs_id(EcsType)); + ecs_type(EcsIdentifier) = flecs_bootstrap_type(world, ecs_id(EcsIdentifier)); +} + +/** Initialize component table. This table is manually constructed to bootstrap + * flecs. After this function has been called, the builtin components can be + * created. + * The reason this table is constructed manually is because it requires the size + * and alignment of the EcsComponent and EcsIdentifier components, which haven't + * been created yet */ +static +ecs_table_t* bootstrap_component_table( + ecs_world_t *world) +{ + ecs_entity_t entities[] = { + ecs_id(EcsComponent), + ecs_pair(ecs_id(EcsIdentifier), EcsName), + ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), + ecs_pair(EcsChildOf, EcsFlecsCore) + }; + + ecs_ids_t array = { + .array = entities, + .count = 4 + }; + + ecs_table_t *result = flecs_table_find_or_create(world, &array); + ecs_data_t *data = flecs_table_get_or_create_data(result); + + /* Preallocate enough memory for initial components */ + data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); + data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); + + data->columns = ecs_os_malloc_n(ecs_column_t, 3); + ecs_assert(data->columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); + data->columns[0].size = ECS_SIZEOF(EcsComponent); + data->columns[0].alignment = ECS_ALIGNOF(EcsComponent); + data->columns[1].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + data->columns[1].size = ECS_SIZEOF(EcsIdentifier); + data->columns[1].alignment = ECS_ALIGNOF(EcsIdentifier); + data->columns[2].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); + data->columns[2].size = ECS_SIZEOF(EcsIdentifier); + data->columns[2].alignment = ECS_ALIGNOF(EcsIdentifier); + + result->column_count = 3; + + return result; +} + +static +void 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_set_name(world, id, name); + ecs_set_symbol(world, id, symbol); + + ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add_pair(world, id, EcsChildOf, parent); + + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup_fullpath(world, name) == id, + ECS_INTERNAL_ERROR, NULL); + } +} + +void flecs_bootstrap( + ecs_world_t *world) +{ + ecs_type(EcsComponent) = NULL; + + ecs_trace_1("bootstrap core components"); + ecs_log_push(); + + /* Create table for initial components */ + ecs_table_t *table = bootstrap_component_table(world); + assert(table != NULL); + + bootstrap_component(world, table, EcsIdentifier); + bootstrap_component(world, table, EcsComponent); + bootstrap_component(world, table, EcsComponentLifecycle); + bootstrap_component(world, table, EcsType); + bootstrap_component(world, table, EcsQuery); + bootstrap_component(world, table, EcsTrigger); + bootstrap_component(world, table, EcsObserver); + + ecs_set_component_actions(world, EcsIdentifier, { + .ctor = ecs_ctor(EcsIdentifier), + .dtor = ecs_dtor(EcsIdentifier), + .copy = ecs_copy(EcsIdentifier), + .move = ecs_move(EcsIdentifier), + .on_set = ecs_on_set(EcsIdentifier) + }); + + ecs_set_component_actions(world, EcsTrigger, { + .ctor = ecs_ctor(EcsTrigger), + .dtor = ecs_dtor(EcsTrigger), + .copy = ecs_copy(EcsTrigger), + .move = ecs_move(EcsTrigger) + }); + + ecs_set_component_actions(world, EcsObserver, { + .ctor = ecs_ctor(EcsObserver), + .dtor = ecs_dtor(EcsObserver), + .copy = ecs_copy(EcsObserver), + .move = ecs_move(EcsObserver) + }); + + world->stats.last_component_id = EcsFirstUserComponentId; + world->stats.last_id = EcsFirstUserEntityId; + world->stats.min_id = 0; + world->stats.max_id = 0; + + bootstrap_types(world); + + ecs_set_scope(world, EcsFlecsCore); + + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); + + flecs_bootstrap_tag(world, EcsModule); + flecs_bootstrap_tag(world, EcsPrefab); + flecs_bootstrap_tag(world, EcsHidden); + flecs_bootstrap_tag(world, EcsDisabled); + + /* Initialize scopes */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); + + /* Initialize builtin entities */ + bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); + bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); + bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); + bootstrap_entity(world, EcsTransitive, "Transitive", EcsFlecsCore); + bootstrap_entity(world, EcsFinal, "Final", EcsFlecsCore); + bootstrap_entity(world, EcsTag, "Tag", EcsFlecsCore); + + bootstrap_entity(world, EcsIsA, "IsA", EcsFlecsCore); + bootstrap_entity(world, EcsChildOf, "ChildOf", EcsFlecsCore); + + bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); + bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); + bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); + bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); + + bootstrap_entity(world, EcsOnDelete, "OnDelete", EcsFlecsCore); + + // bootstrap_entity(world, EcsOnCreateTable, "OnCreateTable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteTable, "OnDeleteTable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); + // bootstrap_entity(world, EcsOnTableNonEmpty, "OnTableNonEmpty", EcsFlecsCore); + // bootstrap_entity(world, EcsOnCreateTrigger, "OnCreateTrigger", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteTrigger, "OnDeleteTrigger", EcsFlecsCore); + // bootstrap_entity(world, EcsOnDeleteObservable, "OnDeleteObservable", EcsFlecsCore); + // bootstrap_entity(world, EcsOnComponentLifecycle, "OnComponentLifecycle", EcsFlecsCore); + + bootstrap_entity(world, EcsOnDeleteObject, "OnDeleteObject", EcsFlecsCore); + + bootstrap_entity(world, EcsRemove, "Remove", EcsFlecsCore); + bootstrap_entity(world, EcsDelete, "Delete", EcsFlecsCore); + bootstrap_entity(world, EcsThrow, "Throw", EcsFlecsCore); + + + /* Transitive relations */ + ecs_add_id(world, EcsIsA, EcsTransitive); + + /* Tag relations (relations that cannot have data) */ + ecs_add_id(world, EcsIsA, EcsTag); + ecs_add_id(world, EcsChildOf, EcsTag); + + /* Final components/relations */ + ecs_add_id(world, ecs_id(EcsComponent), EcsFinal); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsFinal); + ecs_add_id(world, EcsTransitive, EcsFinal); + ecs_add_id(world, EcsFinal, EcsFinal); + ecs_add_id(world, EcsIsA, EcsFinal); + ecs_add_id(world, EcsOnDelete, EcsFinal); + ecs_add_id(world, EcsOnDeleteObject, EcsFinal); + + + /* Define triggers for when relationship cleanup rules are assigned */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsOnDelete, EcsWildcard)}, + .callback = register_on_delete, + .events = {EcsOnAdd} + }); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(EcsOnDeleteObject, EcsWildcard)}, + .callback = register_on_delete_object, + .events = {EcsOnAdd} + }); + + /* Define trigger for when component lifecycle is set for component */ + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_id(EcsComponentLifecycle)}, + .callback = on_set_component_lifecycle, + .events = {EcsOnSet} + }); + + /* Removal of ChildOf objects (parents) deletes the subject (child) */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); + + /* Run bootstrap functions for other parts of the code */ + flecs_bootstrap_hierarchy(world); + + ecs_set_scope(world, 0); + + ecs_log_pop(); +} diff --git a/fggl/ecs2/flecs/src/entity.c b/fggl/ecs2/flecs/src/entity.c new file mode 100644 index 0000000000000000000000000000000000000000..609bdd22584454477aca46537bc66ba518bd7ca8 --- /dev/null +++ b/fggl/ecs2/flecs/src/entity.c @@ -0,0 +1,4230 @@ + +#include "private_api.h" + +static +const ecs_entity_t* new_w_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *component_ids, + int32_t count, + void **c_info, + int32_t *row_out); + +static +void* get_component_w_index( + ecs_table_t *table, + int32_t column_index, + int32_t row) +{ + ecs_assert(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); + + ecs_data_t *data = flecs_table_get_data(table); + ecs_column_t *column = &data->columns[column_index]; + + /* If size is 0, component does not have a value. This is likely caused by + * an application trying to call ecs_get with a tag. */ + int32_t size = column->size; + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + + void *ptr = ecs_vector_first_t(column->data, size, column->alignment); + return ECS_OFFSET(ptr, size * row); +} + +static +void* get_component( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + ecs_table_record_t *tr = ecs_map_get(idr->table_index, + ecs_table_record_t, table->id); + if (!tr) { + return NULL; + } + + return get_component_w_index(table, tr->column, row); +} + +static +void* get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_map_t *table_index, + ecs_map_t *table_index_isa, + int32_t recur_depth) +{ + /* Cycle detected in IsA relation */ + ecs_assert(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); + + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { + return NULL; + } + + /* Exclude Name */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { + return NULL; + } + + /* Should always be an id record for IsA, otherwise a table with a + * HasBase flag set should not exist. */ + if (!table_index_isa) { + ecs_id_record_t *idr = flecs_get_id_record(world, ecs_pair(EcsIsA, EcsWildcard)); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + table_index_isa = idr->table_index; + } + + /* 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 = ecs_map_get( + table_index_isa, ecs_table_record_t, table->id); + ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t type = table->type; + ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); + int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; + void *ptr = NULL; + + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_object(world, pair); + + ecs_record_t *r = ecs_eis_get(world, base); + if (!r) { + continue; + } + + table = r->table; + if (!table) { + continue; + } + + ecs_table_record_t *tr = ecs_map_get(table_index, + ecs_table_record_t, table->id); + if (!tr) { + ptr = get_base_component(world, table, id, table_index, + table_index_isa, recur_depth + 1); + } else { + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + ptr = get_component_w_index(table, tr->column, row); + } + } while (!ptr && (i < end)); + + return ptr; +} + +/* Utility to compute actual row from row in record */ +static +int32_t set_row_info( + ecs_entity_info_t *info, + int32_t row) +{ + return info->row = flecs_record_to_row(row, &info->is_watched); +} + +/* Utility to set info from main stage record */ +static +void set_info_from_record( + ecs_entity_info_t * info, + ecs_record_t * record) +{ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + + info->record = record; + + ecs_table_t *table = record->table; + + set_row_info(info, record->row); + + info->table = table; + if (!info->table) { + return; + } + + ecs_data_t *data = flecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + info->data = data; + + ecs_assert(ecs_vector_count(data->entities) > info->row, + ECS_INTERNAL_ERROR, NULL); +} + +static +const ecs_type_info_t *get_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + return flecs_get_c_info(world, real_id); + } else { + return NULL; + } +} + +static +int get_column_info( + ecs_world_t * world, + ecs_table_t * table, + ecs_ids_t * components, + ecs_column_info_t * cinfo, + bool get_all) +{ + int32_t column_count = table->column_count; + ecs_entity_t *type_array = ecs_vector_first(table->type, ecs_entity_t); + + if (get_all) { + int32_t i, count = ecs_vector_count(table->type); + for (i = 0; i < count; i ++) { + ecs_entity_t id = type_array[i]; + cinfo[i].id = id; + cinfo[i].ci = get_c_info(world, id); + cinfo[i].column = i; + } + + return count; + } else { + ecs_entity_t *array = components->array; + int32_t i, cur, count = components->count; + for (i = 0; i < count; i ++) { + ecs_entity_t id = array[i]; + cinfo[i].id = id; + cinfo[i].ci = get_c_info(world, id); + cinfo[i].column = -1; + + for (cur = 0; cur < column_count; cur ++) { + if (type_array[cur] == id) { + cinfo[i].column = cur; + break; + } + } + } + + return count; + } +} + +#ifdef FLECS_SYSTEM +static +void run_set_systems_for_entities( + ecs_world_t * world, + ecs_ids_t * components, + ecs_table_t * table, + int32_t row, + int32_t count, + ecs_entity_t * entities, + bool set_all) +{ + if (set_all) { + /* Run OnSet systems for all components of the entity. This usually + * happens when an entity is created directly in its target table. */ + ecs_vector_t *queries = table->on_set_all; + ecs_vector_each(queries, ecs_matched_query_t, m, { + flecs_run_monitor(world, m, components, row, count, entities); + }); + } else { + /* Run OnSet systems for a specific component. This usually happens when + * an application calls ecs_set or ecs_modified. The entity's table + * stores a vector for each component with the OnSet systems for that + * component. This vector maintains the same order as the table's type, + * which makes finding the correct set of systems as simple as getting + * the index of a component id in the table type. + * + * One thing to note is that the system may be invoked for a table that + * is not the same as the entity for which the system is invoked. This + * can happen in the case of instancing, where adding an IsA + * relationship conceptually adds components to an entity, but the + * actual components are stored on the base entity. */ + ecs_vector_t **on_set_systems = table->on_set; + if (on_set_systems) { + int32_t index = ecs_type_index_of(table->type, 0, components->array[0]); + + /* This should never happen, as an OnSet system should only ever be + * invoked for entities that have the component for which this + * function was invoked. */ + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *queries = on_set_systems[index]; + ecs_vector_each(queries, ecs_matched_query_t, m, { + flecs_run_monitor(world, m, components, row, count, entities); + }); + } + } +} +#endif + +static +void notify( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_entity_t event, + ecs_ids_t *ids) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t *arr = ids->array; + int32_t arr_count = ids->count; + + int i; + for (i = 0; i < arr_count; i ++) { + flecs_triggers_notify(world, arr[i], event, table, data, row, count); + } +} + +static +void instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count); + +static +void instantiate_children( + ecs_world_t * world, + ecs_entity_t base, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_table_t * child_table) +{ + ecs_type_t type = child_table->type; + ecs_data_t *child_data = flecs_table_get_data(child_table); + if (!child_data || !flecs_table_data_count(child_data)) { + return; + } + + int32_t column_count = child_table->column_count; + ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); + int32_t type_count = ecs_vector_count(type); + + /* Instantiate child table for each instance */ + + /* Create component array for creating the table */ + ecs_ids_t components = { + .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) + }; + + void **c_info = ecs_os_alloca_n(void*, column_count); + + /* 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 i, base_index = -1, pos = 0; + + for (i = 0; i < type_count; i ++) { + ecs_entity_t c = type_array[i]; + + /* Make sure instances don't have EcsPrefab */ + if (c == EcsPrefab) { + 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(c, EcsChildOf) && (ecs_entity_t_lo(c) == base)) { + base_index = pos; + } + + /* Store pointer to component array. We'll use this component array to + * create our new entities in bulk with new_w_data */ + if (i < column_count) { + ecs_column_t *column = &child_data->columns[i]; + c_info[pos] = ecs_vector_first_t( + column->data, column->size, column->alignment); + } else if (pos < column_count) { + c_info[pos] = NULL; + } + + components.array[pos] = c; + pos ++; + } + + ecs_assert(base_index != -1, ECS_INTERNAL_ERROR, NULL); + + /* If children are added to a prefab, make sure they are prefabs too */ + if (table->flags & EcsTableIsPrefab) { + components.array[pos] = EcsPrefab; + pos ++; + } + + components.count = pos; + + /* Instantiate the prefab child table for each new instance */ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t child_count = ecs_vector_count(child_data->entities); + + for (i = row; i < count + row; i ++) { + ecs_entity_t instance = entities[i]; + + /* Replace ChildOf element in the component array with instance id */ + components.array[base_index] = ecs_pair(EcsChildOf, instance); + + /* Find or create table */ + ecs_table_t *i_table = flecs_table_find_or_create(world, &components); + ecs_assert(i_table != NULL, 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. */ + int j; + ecs_entity_t *children = ecs_vector_first( + child_data->entities, ecs_entity_t); +#ifndef NDEBUG + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_assert(child != instance, ECS_INVALID_PARAMETER, NULL); + } +#endif + + /* Create children */ + int32_t child_row; + new_w_data(world, i_table, NULL, child_count, c_info, &child_row); + + /* If prefab child table has children itself, recursively instantiate */ + ecs_data_t *i_data = flecs_table_get_data(i_table); + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + instantiate(world, child, i_table, i_data, child_row + j, 1); + } + } +} + +static +void instantiate( + ecs_world_t * world, + ecs_entity_t base, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count) +{ + /* If base is a parent, instantiate children of base for instances */ + const ecs_id_record_t *r = flecs_get_id_record( + world, ecs_pair(EcsChildOf, base)); + + if (r && r->table_index) { + ecs_table_record_t *tr; + ecs_map_iter_t it = ecs_map_iter(r->table_index); + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + instantiate_children( + world, base, table, data, row, count, tr->table); + } + } +} + +static +bool override_component( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_t type, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count); + +static +bool override_from_base( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t component, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_info_t base_info; + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + + if (!flecs_get_info(world, base, &base_info) || !base_info.table) { + return false; + } + + void *base_ptr = get_component( + world, base_info.table, base_info.row, component); + if (base_ptr) { + int16_t data_size = column->size; + void *data_array = ecs_vector_first_t( + column->data, column->size, column->alignment); + void *data_ptr = ECS_OFFSET(data_array, data_size * row); + + component = ecs_get_typeid(world, component); + const ecs_type_info_t *cdata = flecs_get_c_info(world, component); + int32_t index; + + ecs_copy_t copy = cdata ? cdata->lifecycle.copy : NULL; + if (copy) { + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + void *ctx = cdata->lifecycle.ctx; + for (index = 0; index < count; index ++) { + copy(world, component, &entities[row], &base, + data_ptr, base_ptr, flecs_to_size_t(data_size), 1, ctx); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } + } else { + for (index = 0; index < count; index ++) { + ecs_os_memcpy(data_ptr, base_ptr, data_size); + data_ptr = ECS_OFFSET(data_ptr, data_size); + } + } + + return true; + } else { + /* If component not found on base, check if base itself inherits */ + ecs_type_t base_type = base_info.table->type; + return override_component(world, component, base_type, data, column, + row, count); + } +} + +static +bool override_component( + ecs_world_t * world, + ecs_entity_t component, + ecs_type_t type, + ecs_data_t * data, + ecs_column_t * column, + int32_t row, + int32_t count) +{ + ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); + int32_t i, type_count = ecs_vector_count(type); + + /* Walk prefabs */ + i = type_count - 1; + do { + ecs_entity_t e = type_array[i]; + + if (!(e & ECS_ROLE_MASK)) { + break; + } + + if (ECS_HAS_RELATION(e, EcsIsA)) { + if (override_from_base(world, ecs_pair_object(world, e), component, + data, column, row, count)) + { + return true; + } + } + } while (--i >= 0); + + return false; +} + +static +void components_override( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_column_info_t * component_info, + int32_t component_count, + bool run_on_set) +{ + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(component_info != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table_without_base = table; + ecs_column_t *columns = data->columns; + ecs_type_t type = table->type; + int32_t column_count = table->column_count; + + int i; + for (i = 0; i < component_count; i ++) { + ecs_entity_t component = component_info[i].id; + + if (component >= ECS_HI_COMPONENT_ID) { + if (ECS_HAS_RELATION(component, EcsIsA)) { + ecs_entity_t base = ECS_PAIR_OBJECT(component); + + /* Illegal to create an instance of 0 */ + ecs_assert(base != 0, ECS_INVALID_PARAMETER, NULL); + instantiate(world, base, table, data, row, count); + + /* If table has on_set systems, get table without the base + * entity that was just added. This is needed to determine the + * diff between the on_set systems of the current table and the + * table without the base, as these are the systems that need to + * be invoked */ + ecs_ids_t to_remove = { + .array = &component, + .count = 1 + }; + + table_without_base = flecs_table_traverse_remove(world, + table_without_base, &to_remove, NULL); + } + } + + int32_t column_index = component_info[i].column; + if (column_index == -1 || column_index >= column_count) { + continue; + } + + /* column_index is lower than column count, which means we must have + * data columns */ + ecs_assert(data->columns != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!data->columns[column_index].size) { + continue; + } + + ecs_column_t *column = &columns[column_index]; + if (override_component(world, component, type, data, column, + row, count)) + { + ecs_ids_t to_remove = { + .array = &component, + .count = 1 + }; + table_without_base = flecs_table_traverse_remove(world, + table_without_base, &to_remove, NULL); + } + } + + /* Run OnSet actions when a base entity is added to the entity for + * components not overridden by the entity. */ + if (run_on_set && table_without_base != table) { + flecs_run_monitors(world, table, table->on_set_all, row, count, + table_without_base->on_set_all); + } +} + +static +void set_switch( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_ids_t *entities, + bool reset) +{ + ecs_entity_t *array = entities->array; + int32_t i, comp_count = entities->count; + + for (i = 0; i < comp_count; i ++) { + ecs_entity_t e = array[i]; + + if (ECS_HAS_ROLE(e, CASE)) { + e = e & ECS_COMPONENT_MASK; + + ecs_entity_t sw_case = 0; + if (!reset) { + sw_case = e; + ecs_assert(sw_case != 0, ECS_INTERNAL_ERROR, NULL); + } + + int32_t sw_index = flecs_table_switch_from_case(world, table, e); + ecs_assert(sw_index != -1, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = data->sw_columns[sw_index].data; + ecs_assert(sw != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t r; + for (r = 0; r < count; r ++) { + flecs_switch_set(sw, row + r, sw_case); + } + } + } +} + +static +void ecs_components_switch( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_ids_t *added, + ecs_ids_t *removed) +{ + if (added) { + set_switch(world, table, data, row, count, added, false); + } + if (removed) { + set_switch(world, table, data, row, count, removed, true); + } +} + +static +int32_t new_entity( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * new_table, + ecs_ids_t * added, + bool construct) +{ + ecs_record_t *record = info->record; + ecs_data_t *new_data = flecs_table_get_or_create_data(new_table); + int32_t new_row; + + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!record) { + record = ecs_eis_ensure(world, entity); + } + + new_row = flecs_table_append( + world, new_table, new_data, entity, record, construct); + + record->table = new_table; + record->row = flecs_row_to_record(new_row, info->is_watched); + + ecs_assert( + ecs_vector_count(new_data[0].entities) > new_row, + ECS_INTERNAL_ERROR, NULL); + + if (new_table->flags & EcsTableHasAddActions) { + flecs_run_add_actions( + world, new_table, new_data, new_row, 1, added, true, true); + + if (new_table->flags & EcsTableHasMonitors) { + flecs_run_monitors( + world, new_table, new_table->monitors, new_row, 1, NULL); + } + } + + info->data = new_data; + + return new_row; +} + +static +int32_t move_entity( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * src_table, + ecs_data_t * src_data, + int32_t src_row, + ecs_table_t * dst_table, + ecs_ids_t * added, + ecs_ids_t * removed, + bool construct) +{ + ecs_data_t *dst_data = flecs_table_get_or_create_data(dst_table); + ecs_assert(src_data != dst_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *record = info->record; + ecs_assert(!record || record == ecs_eis_get(world, entity), + ECS_INTERNAL_ERROR, NULL); + + int32_t dst_row = flecs_table_append(world, dst_table, dst_data, entity, + record, false); + + ecs_assert(ecs_vector_count(src_data->entities) > src_row, + ECS_INTERNAL_ERROR, NULL); + + /* Copy entity & components from src_table to dst_table */ + if (src_table->type) { + /* If components were removed, invoke remove actions before deleting */ + if (removed && (src_table->flags & EcsTableHasRemoveActions)) { + /* If entity was moved, invoke UnSet monitors for each component that + * the entity no longer has */ + flecs_run_monitors(world, dst_table, src_table->un_set_all, + src_row, 1, dst_table->un_set_all); + + flecs_run_remove_actions( + world, src_table, src_data, src_row, 1, removed); + } + + flecs_table_move(world, entity, entity, dst_table, dst_data, dst_row, + src_table, src_data, src_row, construct); + } + + /* Update entity index & delete old data after running remove actions */ + record->table = dst_table; + record->row = flecs_row_to_record(dst_row, info->is_watched); + + flecs_table_delete(world, src_table, src_data, src_row, false); + + /* If components were added, invoke add actions */ + if (src_table != dst_table || (added && added->count)) { + if (added && (dst_table->flags & EcsTableHasAddActions)) { + flecs_run_add_actions( + world, dst_table, dst_data, dst_row, 1, added, false, true); + } + + /* Run monitors */ + if (dst_table->flags & EcsTableHasMonitors) { + flecs_run_monitors(world, dst_table, dst_table->monitors, dst_row, + 1, src_table->monitors); + } + + /* If removed components were overrides, run OnSet systems for those, as + * the value of those components changed from the removed component to + * the value of component on the base entity */ + if (removed && dst_table->flags & EcsTableHasIsA) { + flecs_run_monitors(world, dst_table, src_table->on_set_override, + dst_row, 1, dst_table->on_set_override); + } + } + + info->data = dst_data; + + return dst_row; +} + +static +void delete_entity( + ecs_world_t * world, + ecs_table_t * src_table, + ecs_data_t * src_data, + int32_t src_row, + ecs_ids_t * removed) +{ + if (removed) { + flecs_run_monitors(world, src_table, src_table->un_set_all, + src_row, 1, NULL); + + /* Invoke remove actions before deleting */ + if (src_table->flags & EcsTableHasRemoveActions) { + flecs_run_remove_actions( + world, src_table, src_data, src_row, 1, removed); + } + } + + flecs_table_delete(world, src_table, src_data, src_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 update_component_monitor_w_array( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t relation, + ecs_ids_t * entities) +{ + if (!entities) { + return; + } + + int i; + for (i = 0; i < entities->count; i ++) { + ecs_entity_t id = entities->array[i]; + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(id); + + /* If a relationship has changed, check if it could have impacted + * the shape of the graph for that relationship. If so, mark the + * relationship as dirty */ + if (rel != relation && flecs_get_id_record(world, ecs_pair(rel, entity))) { + update_component_monitor_w_array(world, entity, rel, entities); + } + + } + + if (ECS_HAS_RELATION(id, EcsIsA)) { + /* If an IsA relationship is added to a monitored entity (can + * be either a parent or a base) component monitors need to be + * evaluated for the components of the prefab. */ + ecs_entity_t base = ecs_pair_object(world, id); + ecs_type_t type = ecs_get_type(world, base); + ecs_ids_t base_entities = flecs_type_to_ids(type); + + /* This evaluates the component monitor for all components of the + * base entity. If the base entity contains IsA relationships + * these will be evaluated recursively as well. */ + update_component_monitor_w_array( + world, entity, relation, &base_entities); + } else { + flecs_monitor_mark_dirty(world, relation, id); + } + } +} + +static +void update_component_monitors( + ecs_world_t * world, + ecs_entity_t entity, + ecs_ids_t * added, + ecs_ids_t * removed) +{ + update_component_monitor_w_array(world, entity, 0, added); + update_component_monitor_w_array(world, entity, 0, removed); +} + +static +void commit( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_table_t * dst_table, + ecs_ids_t * added, + ecs_ids_t * removed, + bool construct) +{ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = info->table; + if (src_table == dst_table) { + /* If source and destination table are the same no action is needed * + * However, if a component was added in the process of traversing a + * table, this suggests that a case switch could have occured. */ + if (((added && added->count) || (removed && removed->count)) && + src_table && src_table->flags & EcsTableHasSwitch) + { + ecs_components_switch( + world, src_table, info->data, info->row, 1, added, removed); + } + + return; + } + + if (src_table) { + ecs_data_t *src_data = info->data; + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (dst_table->type) { + info->row = move_entity(world, entity, info, src_table, + src_data, info->row, dst_table, added, removed, construct); + info->table = dst_table; + } else { + delete_entity(world, src_table, src_data, info->row, removed); + + ecs_eis_set(world, entity, &(ecs_record_t){ + NULL, (info->is_watched == true) * -1 + }); + } + } else { + if (dst_table->type) { + info->row = new_entity( + world, entity, info, dst_table, added, construct); + info->table = dst_table; + } + } + + /* If the entity is being watched, it is being monitored for changes and + * requires rematching systems when components are added or removed. This + * ensures that systems that rely on components from containers or prefabs + * update the matched tables when the application adds or removes a + * component from, for example, a container. */ + if (info->is_watched) { + update_component_monitors(world, entity, added, removed); + } + + if ((!src_table || !src_table->type) && world->range_check_enabled) { + ecs_assert(!world->stats.max_id || entity <= world->stats.max_id, ECS_OUT_OF_RANGE, 0); + ecs_assert(entity >= world->stats.min_id, ECS_OUT_OF_RANGE, 0); + } +} + +static +void new( + ecs_world_t * world, + ecs_entity_t entity, + ecs_ids_t * to_add) +{ + ecs_entity_info_t info = {0}; + ecs_table_t *table = flecs_table_traverse_add( + world, &world->store.root, to_add, NULL); + new_entity(world, entity, &info, table, to_add, true); +} + +static +const ecs_entity_t* new_w_data( + ecs_world_t * world, + ecs_table_t * table, + ecs_ids_t * component_ids, + int32_t count, + void ** component_data, + int32_t * row_out) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + int32_t sparse_count = ecs_eis_count(world); + const ecs_entity_t *ids = flecs_sparse_new_ids(world->store.entity_index, count); + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t type = table->type; + + if (!type) { + return ids; + } + + ecs_ids_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = ecs_vector_first(type, ecs_entity_t); + component_array.count = ecs_vector_count(type); + } + + ecs_data_t *data = flecs_table_get_or_create_data(table); + int32_t row = flecs_table_appendn(world, table, data, count, ids); + ecs_ids_t added = flecs_type_to_ids(type); + + /* Update entity index. */ + int i; + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + for (i = 0; i < count; i ++) { + record_ptrs[row + i] = ecs_eis_set(world, ids[i], + &(ecs_record_t){ + .table = table, + .row = row + i + 1 + }); + } + + flecs_defer_none(world, &world->stage); + + flecs_run_add_actions(world, table, data, row, count, &added, + true, component_data == NULL); + + if (component_data) { + /* Set components that we're setting in the component mask so the init + * actions won't call OnSet triggers for them. This ensures we won't + * call OnSet triggers multiple times for the same component */ + int32_t c_i; + for (c_i = 0; c_i < component_ids->count; c_i ++) { + ecs_entity_t c = component_ids->array[c_i]; + + /* Bulk copy column data into new table */ + int32_t table_index = ecs_type_index_of(type, 0, c); + ecs_assert(table_index >= 0, ECS_INTERNAL_ERROR, NULL); + if (table_index >= table->column_count) { + continue; + } + + ecs_column_t *column = &data->columns[table_index]; + int16_t size = column->size; + if (!size) { + continue; + } + + int16_t alignment = column->alignment; + void *ptr = ecs_vector_first_t(column->data, size, alignment); + ptr = ECS_OFFSET(ptr, size * row); + + /* Copy component data */ + void *src_ptr = component_data[c_i]; + if (!src_ptr) { + continue; + } + + const ecs_type_info_t *cdata = get_c_info(world, c); + ecs_copy_t copy; + if (cdata && (copy = cdata->lifecycle.copy)) { + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + copy(world, c, entities, entities, ptr, src_ptr, + flecs_to_size_t(size), count, cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(ptr, src_ptr, size * count); + } + }; + + flecs_run_set_systems(world, 0, table, data, NULL, row, count, true); + } + + flecs_run_monitors(world, table, table->monitors, row, count, NULL); + + flecs_defer_flush(world, &world->stage); + + if (row_out) { + *row_out = row; + } + + ids = flecs_sparse_ids(world->store.entity_index); + + return &ids[sparse_count]; +} + +static +bool has_type( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type, + bool match_any, + bool match_prefabs) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return false; + } + + if (!type) { + return true; + } + + ecs_type_t entity_type = ecs_get_type(world, entity); + + return flecs_type_contains( + world, entity_type, type, match_any, match_prefabs) != 0; +} + +static +void add_remove( + ecs_world_t * world, + ecs_entity_t entity, + ecs_ids_t * to_add, + ecs_ids_t * to_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + ecs_assert(to_add->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_assert(to_remove->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t add_buffer[ECS_MAX_ADD_REMOVE]; + ecs_entity_t remove_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = add_buffer }; + ecs_ids_t removed = { .array = remove_buffer }; + + ecs_table_t *src_table = info.table; + + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, to_remove, &removed); + + dst_table = flecs_table_traverse_add( + world, dst_table, to_add, &added); + + commit(world, entity, &info, dst_table, &added, &removed, true); +} + +static +void add_ids_w_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_ids_t * components, + bool construct) +{ + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = buffer }; + + ecs_table_t *src_table = info->table; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, components, &added); + + commit(world, entity, info, dst_table, &added, NULL, construct); +} + +static +void remove_ids_w_info( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info, + ecs_ids_t * components) +{ + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = buffer }; + + ecs_table_t *src_table = info->table; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, components, &removed); + + commit(world, entity, info, dst_table, NULL, &removed, true); +} + +static +void add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_ids_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_add(world, stage, entity, components)) { + return; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = buffer }; + + ecs_table_t *src_table = info.table; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, components, &added); + + commit(world, entity, &info, dst_table, &added, NULL, true); + + flecs_defer_flush(world, stage); +} + +static +void remove_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_ids_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(components->count < ECS_MAX_ADD_REMOVE, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_remove(world, stage, entity, components)) { + return; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = buffer }; + + ecs_table_t *src_table = info.table; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, components, &removed); + + commit(world, entity, &info, dst_table, NULL, &removed, true); + + flecs_defer_flush(world, stage); +} + +static +void *get_mutable( + ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_t component, + ecs_entity_info_t * info, + bool * is_added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(component != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert((component & ECS_COMPONENT_MASK) == component || + ECS_HAS_ROLE(component, PAIR), ECS_INVALID_PARAMETER, NULL); + + void *dst = NULL; + if (flecs_get_info(world, entity, info) && info->table) { + dst = get_component(world, info->table, info->row, component); + } + + if (!dst) { + ecs_table_t *table = info->table; + + ecs_ids_t to_add = { + .array = &component, + .count = 1 + }; + + add_ids_w_info(world, entity, info, &to_add, true); + + flecs_get_info(world, entity, info); + ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL); + + dst = get_component(world, info->table, info->row, component); + + if (is_added) { + *is_added = table != info->table; + } + + return dst; + } else { + if (is_added) { + *is_added = false; + } + + return dst; + } +} + + +/* -- Private functions -- */ + +void flecs_run_add_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_ids_t * added, + bool get_all, + bool run_on_set) +{ + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (table->flags & EcsTableHasIsA) { + ecs_column_info_t cinfo[ECS_MAX_ADD_REMOVE]; + + int added_count = get_column_info( + world, table, added, cinfo, get_all); + + components_override( + world, table, data, row, count, cinfo, + added_count, run_on_set); + } + + if (table->flags & EcsTableHasSwitch) { + ecs_components_switch(world, table, data, row, count, added, NULL); + } + + if (table->flags & EcsTableHasOnAdd) { + notify(world, table, data, row, count, EcsOnAdd, added); + } +} + +void flecs_run_remove_actions( + ecs_world_t * world, + ecs_table_t * table, + ecs_data_t * data, + int32_t row, + int32_t count, + ecs_ids_t * removed) +{ + ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); + + if (count) { + if (table->flags & EcsTableHasUnSet) { + notify(world, table, data, row, count, EcsUnSet, removed); + } + if (table->flags & EcsTableHasOnRemove) { + notify(world, table, data, row, count, EcsOnRemove, removed); + } + } +} + +bool flecs_get_info( + const ecs_world_t * world, + ecs_entity_t entity, + ecs_entity_info_t * info) +{ + info->table = NULL; + info->record = NULL; + info->data = NULL; + info->is_watched = false; + + if (entity & ECS_ROLE) { + return false; + } + + ecs_record_t *record = ecs_eis_get(world, entity); + + if (!record) { + return false; + } + + set_info_from_record(info, record); + + return true; +} + +void flecs_run_set_systems( + ecs_world_t *world, + ecs_id_t component, + ecs_table_t *table, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count, + bool set_all) +{ + ecs_assert(set_all || column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!column || column->size != 0, ECS_INTERNAL_ERROR, NULL); + + if (!count || !data || (column && !column->size)) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row); + + ecs_ids_t components; + + if (!set_all) { + const ecs_type_info_t *info = get_c_info(world, component); + ecs_on_set_t on_set; + if (info && (on_set = info->lifecycle.on_set)) { + ecs_size_t size = column->size; + void *ptr = ecs_vector_get_t( + column->data, size, column->alignment, row); + on_set(world, component, entities, ptr, flecs_to_size_t(size), count, + info->lifecycle.ctx); + } + + components = (ecs_ids_t){ + .array = &component, + .count = component != 0 + }; + } else { + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t i, column_count = table->column_count; + + for (i = 0; i < column_count; i ++) { + ecs_id_t id = ids[i]; + const ecs_type_info_t *info = get_c_info(world, id); + ecs_on_set_t on_set; + if (info && (on_set = info->lifecycle.on_set)) { + ecs_column_t *c = &data->columns[i]; + ecs_size_t size = c->size; + if (!size) { + continue; + } + + void *ptr = ecs_vector_get_t(c->data, size, c->alignment, row); + on_set(world, ids[i], entities, ptr, flecs_to_size_t(size), + count, info->lifecycle.ctx); + } + } + + components = (ecs_ids_t){ + .array = ids, + .count = column_count + }; + } + +#ifdef FLECS_SYSTEM + run_set_systems_for_entities(world, &components, table, row, + count, entities, set_all); +#endif + + if (table->flags & EcsTableHasOnSet) { + if (!set_all) { + notify(world, table, data, row, count, EcsOnSet, &components); + } else { + int32_t i, column_count = table->column_count; + for (i = 0; i < column_count; i ++) { + ecs_column_t *c = &data->columns[i]; + if (c->size) { + notify(world, table, data, row, count, EcsOnSet, + &components); + } + } + } + } +} + +void flecs_run_monitors( + ecs_world_t * world, + ecs_table_t * dst_table, + ecs_vector_t * v_dst_monitors, + int32_t dst_row, + int32_t count, + ecs_vector_t *v_src_monitors) +{ + (void)world; + (void)dst_table; + (void)v_dst_monitors; + (void)dst_row; + (void)count; + (void)v_src_monitors; + +#ifdef FLECS_SYSTEM + if (v_dst_monitors == v_src_monitors) { + return; + } + + if (!v_dst_monitors) { + return; + } + + ecs_assert(!(dst_table->flags & EcsTableIsPrefab), ECS_INTERNAL_ERROR, NULL); + + if (!v_src_monitors) { + ecs_vector_each(v_dst_monitors, ecs_matched_query_t, monitor, { + flecs_run_monitor(world, monitor, NULL, dst_row, count, NULL); + }); + } else { + /* If both tables have monitors, run the ones that dst_table has and + * src_table doesn't have */ + int32_t i, m_count = ecs_vector_count(v_dst_monitors); + int32_t j = 0, src_count = ecs_vector_count(v_src_monitors); + ecs_matched_query_t *dst_monitors = ecs_vector_first(v_dst_monitors, ecs_matched_query_t); + ecs_matched_query_t *src_monitors = ecs_vector_first(v_src_monitors, ecs_matched_query_t); + + for (i = 0; i < m_count; i ++) { + ecs_matched_query_t *dst = &dst_monitors[i]; + + ecs_entity_t system = dst->query->system; + ecs_assert(system != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_matched_query_t *src = 0; + while (j < src_count) { + src = &src_monitors[j]; + if (src->query->system < system) { + j ++; + } else { + break; + } + } + + if (src && src->query->system == system) { + continue; + } + + flecs_run_monitor(world, dst, NULL, dst_row, count, NULL); + } + } +#endif +} + +int32_t flecs_record_to_row( + int32_t row, + bool *is_watched_out) +{ + bool is_watched = row < 0; + row = row * -(is_watched * 2 - 1) - 1 * (row != 0); + *is_watched_out = is_watched; + return row; +} + +int32_t flecs_row_to_record( + int32_t row, + bool is_watched) +{ + return (row + 1) * -(is_watched * 2 - 1); +} + +ecs_ids_t flecs_type_to_ids( + ecs_type_t type) +{ + return (ecs_ids_t){ + .array = ecs_vector_first(type, ecs_entity_t), + .count = ecs_vector_count(type) + }; +} + +void flecs_set_watch( + ecs_world_t *world, + ecs_entity_t entity) +{ + (void)world; + + ecs_record_t *record = ecs_eis_get(world, entity); + if (!record) { + ecs_record_t new_record = {.row = -1, .table = NULL}; + ecs_eis_set(world, entity, &new_record); + } else { + if (record->row > 0) { + record->row *= -1; + + } else if (record->row == 0) { + /* If entity is empty, there is no index to change the sign of. In + * this case, set the index to -1, and assign an empty type. */ + record->row = -1; + record->table = NULL; + } + } +} + + +/* -- Public functions -- */ + +bool ecs_commit( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + ecs_ids_t *added, + ecs_ids_t *removed) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = NULL; + if (!record) { + record = ecs_eis_get(world, entity); + src_table = record->table; + } + + ecs_entity_info_t info = {0}; + if (record) { + set_info_from_record(&info, record); + } + + commit(world, entity, &info, table, added, removed, true); + + return src_table != table; +} + +ecs_entity_t ecs_new_id( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * since it is thread safe (uses atomic inc when in threading mode) */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_entity_t entity; + + int32_t stage_count = ecs_get_stage_count(unsafe_world); + if (stage->asynchronous || (ecs_os_has_threading() && stage_count > 1)) { + /* Can't atomically increase number above max int */ + ecs_assert( + unsafe_world->stats.last_id < UINT_MAX, ECS_INTERNAL_ERROR, NULL); + + entity = (ecs_entity_t)ecs_os_ainc( + (int32_t*)&unsafe_world->stats.last_id); + } else { + entity = ecs_eis_recycle(unsafe_world); + } + + ecs_assert(!unsafe_world->stats.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->stats.max_id, + ECS_OUT_OF_RANGE, NULL); + + return entity; +} + +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_id_t prev = stage->with; + stage->with = id; + return prev; +} + +ecs_entity_t ecs_get_with( + const ecs_world_t *world) +{ + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->with; +} + +ecs_entity_t ecs_new_component_id( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, 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_world_t*)ecs_get_world(world); + + if (unsafe_world->is_readonly) { + /* Can't issue new comp id while iterating when in multithreaded mode */ + ecs_assert(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_ITERATING, NULL); + } + + ecs_entity_t id; + + if (unsafe_world->stats.last_component_id < ECS_HI_COMPONENT_ID) { + do { + id = unsafe_world->stats.last_component_id ++; + } while (ecs_exists(unsafe_world, id) && id < ECS_HI_COMPONENT_ID); + } + + if (unsafe_world->stats.last_component_id >= ECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + id = ecs_new_id(unsafe_world); + } + + return id; +} + +ecs_entity_t ecs_new_w_type( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + if (!type) { + return ecs_new_w_id(world, 0); + } + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + ecs_id_t with = stage->with; + ecs_entity_t scope = stage->scope; + + ecs_ids_t to_add = flecs_type_to_ids(type); + if (flecs_defer_new(world, stage, entity, &to_add)) { + if (with) { + ecs_add_id(world, entity, with); + } + if (scope) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } + return entity; + } + + new(world, entity, &to_add); + + ecs_id_t ids[2]; + to_add = (ecs_ids_t){ .array = ids, .count = 0 }; + + if (with) { + ids[to_add.count ++] = with; + } + + if (scope) { + ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); + } + + if (to_add.count) { + add_ids(world, entity, &to_add); + } + + flecs_defer_flush(world, stage); + + return entity; +} + +ecs_entity_t ecs_new_w_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new_id(world); + + ecs_id_t ids[3]; + ecs_ids_t to_add = { .array = ids, .count = 0 }; + + if (id) { + ids[to_add.count ++] = id; + } + + ecs_id_t with = stage->with; + if (with) { + ids[to_add.count ++] = with; + } + + ecs_entity_t scope = stage->scope; + if (scope) { + if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { + ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); + } + } + + if (flecs_defer_new(world, stage, entity, &to_add)) { + return entity; + } + + if (to_add.count) { + new(world, entity, &to_add); + } else { + ecs_eis_set(world, entity, &(ecs_record_t){ 0 }); + } + + flecs_defer_flush(world, stage); + + return entity; +} + +#ifdef FLECS_PARSER + +/* Traverse table graph by either adding or removing identifiers parsed from the + * passed in expression. */ +static +ecs_table_t *traverse_from_expr( + ecs_world_t *world, + ecs_table_t *table, + const char *name, + const char *expr, + ecs_ids_t *modified, + bool is_add, + bool replace_and) +{ + int32_t size = modified->count; + if (size < ECS_MAX_ADD_REMOVE) { + size = ECS_MAX_ADD_REMOVE; + } + + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (ecs_term_finalize(world, name, expr, &term)) { + return NULL; + } + + if (!ecs_term_is_trivial(&term)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid non-trivial term in add expression"); + return NULL; + } + + if (modified->count == size) { + size *= 2; + ecs_id_t *arr = ecs_os_malloc(size * ECS_SIZEOF(ecs_id_t)); + ecs_os_memcpy(arr, modified->array, + modified->count * ECS_SIZEOF(ecs_id_t)); + + if (modified->count != ECS_MAX_ADD_REMOVE) { + ecs_os_free(modified->array); + } + + modified->array = arr; + } + + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + ecs_ids_t arr = { .array = &term.id, .count = 1 }; + if (is_add) { + table = flecs_table_traverse_add( + world, table, &arr, modified); + } else { + table = flecs_table_traverse_remove( + world, table, &arr, modified); + } + + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } else if (term.oper == EcsAndFrom) { + /* Add all components from the specified type */ + const EcsType *t = ecs_get(world, term.id, EcsType); + if (!t) { + ecs_parser_error(name, expr, (ptr - expr), + "expected type for AND role"); + return NULL; + } + + ecs_id_t *ids = ecs_vector_first(t->normalized, ecs_id_t); + int32_t i, count = ecs_vector_count(t->normalized); + for (i = 0; i < count; i ++) { + ecs_ids_t arr = { .array = &ids[i], .count = 1 }; + if (is_add) { + table = flecs_table_traverse_add( + world, table, &arr, modified); + } else { + table = flecs_table_traverse_remove( + world, table, &arr, modified); + } + + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + } + + ecs_term_fini(&term); + } + } + + return table; +} + +/* Add/remove components based on the parsed expression. This operation is + * slower than traverse_from_expr, but safe to use from a deferred context. */ +static +void defer_from_expr( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const char *expr, + bool is_add, + bool replace_and) +{ + const char *ptr = expr; + if (ptr) { + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (ecs_term_finalize(world, name, expr, &term)) { + return; + } + + if (!ecs_term_is_trivial(&term)) { + ecs_parser_error(name, expr, (ptr - expr), + "invalid non-trivial term in add expression"); + return; + } + + if (term.oper == EcsAnd || !replace_and) { + /* Regular AND expression */ + if (is_add) { + ecs_add_id(world, entity, term.id); + } else { + ecs_remove_id(world, entity, term.id); + } + } else if (term.oper == EcsAndFrom) { + /* Add all components from the specified type */ + const EcsType *t = ecs_get(world, term.id, EcsType); + if (!t) { + ecs_parser_error(name, expr, (ptr - expr), + "expected type for AND role"); + return; + } + + ecs_id_t *ids = ecs_vector_first(t->normalized, ecs_id_t); + int32_t i, count = ecs_vector_count(t->normalized); + for (i = 0; i < count; i ++) { + if (is_add) { + ecs_add_id(world, entity, ids[i]); + } else { + ecs_remove_id(world, entity, ids[i]); + } + } + } + + ecs_term_fini(&term); + } + } +} +#endif + +/* If operation is not deferred, add/remove components by finding the target + * table and moving the entity towards it. */ +static +void traverse_add_remove( + 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; + + /* Find existing table */ + ecs_entity_info_t info = {0}; + ecs_table_t *src_table = NULL, *table = NULL; + if (!new_entity) { + if (flecs_get_info(world, result, &info)) { + table = info.table; + } + } + + ecs_entity_t added_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = added_buffer }; + + ecs_entity_t removed_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = removed_buffer }; + + /* 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) { + ecs_entity_t id = ecs_pair(EcsChildOf, scope); + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + if (with) { + ecs_ids_t arr = { .array = &with, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + } + + /* If a name is provided but not yet assigned, add the Name component */ + if (name && !name_assigned) { + ecs_entity_t id = ecs_pair(ecs_id(EcsIdentifier), EcsName); + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* Add components from the 'remove' id array */ + i = 0; + ids = desc->remove; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_ids_t arr = { .array = &id, .count = 1 }; + table = flecs_table_traverse_remove(world, table, &arr, &removed); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { +#ifdef FLECS_PARSER + table = traverse_from_expr( + world, table, name, desc->add_expr, &added, true, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Remove components from the 'remove_expr' expression */ + if (desc->remove_expr) { +#ifdef FLECS_PARSER + table = traverse_from_expr( + world, table, name, desc->remove_expr, &removed, false, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Commit entity to destination table */ + if (src_table != table) { + commit(world, result, &info, table, &added, &removed, true); + } + + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, result, scope, name, sep, NULL); + ecs_assert(ecs_get_name(world, result) != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + if (desc->symbol) { + 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 (added.count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(added.array); + } + if (removed.count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(removed.array); + } +} + +/* When in deferred mode, we need to add/remove components one by one using + * the regular operations. */ +static +void deferred_add_remove( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) +{ + const char *sep = desc->sep; + + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (new_entity) { + if (new_entity && scope && !name && !name_assigned) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } + + if (with) { + ecs_add_id(world, entity, with); + } + } + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->add; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_add_id(world, entity, id); + } + + /* Add components from the 'remove' id array */ + i = 0; + ids = desc->remove; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_remove_id(world, entity, id); + } + + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { +#ifdef FLECS_PARSER + defer_from_expr(world, entity, name, desc->add_expr, true, true); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Remove components from the 'remove_expr' expression */ + if (desc->remove_expr) { +#ifdef FLECS_PARSER + defer_from_expr(world, entity, name, desc->remove_expr, true, false); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, entity, scope, name, sep, NULL); + } + + /* Currently it's not supported to set the symbol from a deferred context */ + if (desc->symbol) { + const char *sym = ecs_get_symbol(world, entity); + ecs_assert(!ecs_os_strcmp(sym, desc->symbol), ECS_UNSUPPORTED, NULL); + (void)sym; + } +} + +ecs_entity_t ecs_entity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t scope = ecs_get_scope(world); + ecs_id_t with = ecs_get_with(world); + + const char *name = desc->name; + const char *sep = desc->sep; + if (!sep) { + sep = "."; + } + + const char *root_sep = desc->root_sep; + + bool 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->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; + } + } + } + + /* Find or create entity */ + ecs_entity_t result = desc->entity; + if (!result) { + if (name) { + 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_component_id(world); + } else { + result = ecs_new_id(world); + } + new_entity = true; + ecs_assert(ecs_get_type(world, result) == NULL, + ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_assert(ecs_is_valid(world, result), ECS_INVALID_PARAMETER, NULL); + + name_assigned = ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName); + if (name && name_assigned) { + /* If entity has name, verify that name matches */ + char *path = ecs_get_path_w_sep(world, scope, result, sep, NULL); + if (path) { + if (ecs_os_strcmp(path, name)) { + /* Mismatching name */ + ecs_os_free(path); + return 0; + } + ecs_os_free(path); + } + } + } + + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); + + if (stage->defer) { + deferred_add_remove(world, result, name, desc, + scope, with, new_entity, name_assigned); + } else { + traverse_add_remove(world, result, name, desc, + scope, with, new_entity, name_assigned); + } + + return result; +} + +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = (ecs_world_t*)ecs_get_world(world); + + bool is_readonly = world->is_readonly; + bool is_deferred = ecs_is_deferred(world); + int32_t defer_count = 0; + ecs_vector_t *defer_queue = NULL; + ecs_stage_t *stage = NULL; + + /* If world is readonly or deferring is enabled, component registration can + * still happen directly on the main storage, but only if the application + * is singlethreaded. */ + if (is_readonly || is_deferred) { + ecs_assert(ecs_get_stage_count(world) <= 1, + ECS_INVALID_WHILE_ITERATING, NULL); + + /* Silence readonly warnings */ + world->is_readonly = false; + + /* Hack around safety checks (this ought to look ugly) */ + ecs_world_t *temp_world = world; + stage = flecs_stage_from_world(&temp_world); + defer_count = stage->defer; + defer_queue = stage->defer_queue; + stage->defer = 0; + stage->defer_queue = NULL; + } + + ecs_entity_desc_t entity_desc = desc->entity; + entity_desc.use_low_id = true; + if (!entity_desc.symbol) { + entity_desc.symbol = entity_desc.name; + } + + ecs_entity_t e = desc->entity.entity; + ecs_entity_t result = ecs_entity_init(world, &entity_desc); + if (!result) { + return 0; + } + + bool added = false; + EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent, &added); + + if (added) { + ptr->size = flecs_from_size_t(desc->size); + ptr->alignment = flecs_from_size_t(desc->alignment); + } else { + if (ptr->size != flecs_from_size_t(desc->size)) { + ecs_abort(ECS_INVALID_COMPONENT_SIZE, desc->entity.name); + } + if (ptr->alignment != flecs_from_size_t(desc->alignment)) { + ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, desc->entity.name); + } + } + + ecs_modified(world, result, EcsComponent); + + if (e > world->stats.last_component_id && e < ECS_HI_COMPONENT_ID) { + world->stats.last_component_id = e + 1; + } + + /* Ensure components cannot be deleted */ + ecs_add_pair(world, result, EcsOnDelete, EcsThrow); + + if (is_readonly || is_deferred) { + /* Restore readonly state / defer count */ + world->is_readonly = is_readonly; + stage->defer = defer_count; + stage->defer_queue = defer_queue; + } + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + + return result; +} + +ecs_entity_t ecs_type_init( + ecs_world_t *world, + const ecs_type_desc_t *desc) +{ + ecs_entity_t result = ecs_entity_init(world, &desc->entity); + if (!result) { + return 0; + } + + ecs_table_t *table = NULL, *normalized = NULL; + + ecs_entity_t added_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t added = { .array = added_buffer }; + + /* Find destination table (and type) */ + + /* Add components from the 'add' id array */ + int32_t i = 0; + ecs_id_t id; + const ecs_id_t *ids = desc->ids; + while ((i < ECS_MAX_ADD_REMOVE) && (id = ids[i ++])) { + ecs_ids_t arr = { .array = &id, .count = 1 }; + normalized = flecs_table_traverse_add(world, normalized, &arr, &added); + table = flecs_table_traverse_add(world, table, &arr, &added); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + } + + /* If expression is set, add it to the table */ + if (desc->ids_expr) { +#ifdef FLECS_PARSER + normalized = traverse_from_expr( + world, normalized, desc->entity.name, desc->ids_expr, &added, + true, true); + + table = traverse_from_expr( + world, table, desc->entity.name, desc->ids_expr, &added, + true, false); +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + if (added.count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(added.array); + } + + ecs_type_t type = NULL; + ecs_type_t normalized_type = NULL; + + if (table) { + type = table->type; + } + if (normalized) { + normalized_type = normalized->type; + } + + bool add = false; + EcsType *type_ptr = ecs_get_mut(world, result, EcsType, &add); + if (add) { + type_ptr->type = type; + type_ptr->normalized = normalized_type; + + /* This will allow the type to show up in debug tools */ + if (type) { + ecs_map_set(world->type_handles, (uintptr_t)type, &result); + } + + ecs_modified(world, result, EcsType); + } else { + if (type_ptr->type != type) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + if (type_ptr->normalized != normalized_type) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } + + return result; +} + +const ecs_entity_t* ecs_bulk_new_w_data( + ecs_world_t *world, + int32_t count, + const ecs_ids_t *components, + void * data) +{ + ecs_assert(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, components, data, &ids)) { + return ids; + } + + ecs_table_t *table = flecs_table_find_or_create(world, components); + ids = new_w_data(world, table, NULL, count, data, NULL); + flecs_defer_flush(world, stage); + return ids; +} + +const ecs_entity_t* ecs_bulk_new_w_type( + ecs_world_t *world, + ecs_type_t type, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + const ecs_entity_t *ids; + ecs_ids_t components = flecs_type_to_ids(type); + if (flecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) { + return ids; + } + ecs_table_t *table = ecs_table_from_type(world, type); + ids = new_w_data(world, table, NULL, count, NULL, NULL); + flecs_defer_flush(world, stage); + return ids; +} + +const ecs_entity_t* ecs_bulk_new_w_id( + ecs_world_t *world, + ecs_id_t id, + int32_t count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_ids_t components = { + .array = &id, + .count = 1 + }; + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, &components, NULL, &ids)) { + return ids; + } + ecs_table_t *table = flecs_table_find_or_create(world, &components); + ids = new_w_data(world, table, NULL, count, NULL, NULL); + flecs_defer_flush(world, stage); + return ids; +} + +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(world, stage, entity)) { + return; + } + + ecs_entity_info_t info; + info.table = NULL; + + flecs_get_info(world, entity, &info); + + ecs_table_t *table = info.table; + if (table) { + ecs_type_t type = table->type; + + /* Remove all components */ + ecs_ids_t to_remove = flecs_type_to_ids(type); + remove_ids_w_info(world, entity, &info, &to_remove); + } + + flecs_defer_flush(world, stage); +} + +static +void on_delete_action( + ecs_world_t *world, + ecs_entity_t entity); + +static +void throw_invalid_delete( + ecs_world_t *world, + ecs_id_t id) +{ + char buff[256]; + ecs_id_str(world, id, buff, 256); + ecs_abort(ECS_INVALID_DELETE, buff); +} + +static +void remove_from_table( + ecs_world_t *world, + ecs_table_t *src_table, + ecs_id_t id, + int32_t column, + int32_t column_count) +{ + ecs_entity_t removed_buffer[ECS_MAX_ADD_REMOVE]; + ecs_ids_t removed = { .array = removed_buffer }; + + if (column_count > ECS_MAX_ADD_REMOVE) { + removed.array = ecs_os_malloc_n(ecs_id_t, column_count); + } + + ecs_table_t *dst_table = src_table; + ecs_id_t *ids = ecs_vector_first(src_table->type, ecs_id_t); + + /* If id is pair but the column pointed to is not a pair, the record is + * pointing to an instance of the id that has a (non-PAIR) role. */ + bool is_pair = ECS_HAS_ROLE(id, PAIR); + bool is_role = is_pair && !ECS_HAS_ROLE(ids[column], PAIR); + ecs_assert(!is_role || ((ids[column] & ECS_ROLE_MASK) != 0), + ECS_INTERNAL_ERROR, NULL); + bool is_wildcard = ecs_id_is_wildcard(id); + + int32_t i, count = ecs_vector_count(src_table->type), removed_count = 0; + ecs_entity_t entity = ECS_PAIR_RELATION(id); + + for (i = column; i < count; i ++) { + ecs_id_t e = ids[i]; + + if (is_role) { + if ((e & ECS_COMPONENT_MASK) != entity) { + continue; + } + } else if (is_wildcard && !ecs_id_match(e, id)) { + continue; + } + + ecs_ids_t to_remove = { .array = &e, .count = 1 }; + dst_table = flecs_table_traverse_remove( + world, dst_table, &to_remove, &removed); + + removed_count ++; + if (removed_count == column_count) { + break; + } + } + + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!dst_table->type) { + /* If this removes all components, clear table */ + flecs_table_clear_entities(world, src_table); + } else { + /* Otherwise, merge table into dst_table */ + if (dst_table != src_table) { + ecs_data_t *src_data = flecs_table_get_data(src_table); + int32_t src_count = ecs_table_count(src_table); + if (removed.count && src_data) { + flecs_run_remove_actions(world, src_table, + src_data, 0, src_count, &removed); + } + + ecs_data_t *dst_data = flecs_table_get_data(dst_table); + flecs_table_merge(world, dst_table, src_table, dst_data, src_data); + } + } + + if (column_count > ECS_MAX_ADD_REMOVE) { + ecs_os_free(removed.array); + } +} + +static +void delete_objects( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_data_t *data = flecs_table_get_data(table); + if (data) { + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + int32_t i, count = ecs_vector_count(data->entities); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *r = flecs_sparse_get( + world->store.entity_index, ecs_record_t, e); + + /* If row is negative, it means the entity is being monitored. Only + * monitored entities can have delete actions */ + if (r && r->row < 0) { + /* Make row positive which prevents infinite recursion in case + * of cyclic delete actions */ + r->row = (-r->row); + + /* Run delete actions for objects */ + on_delete_action(world, entities[i]); + } + } + + /* Clear components from table (invokes destructors, OnRemove) */ + flecs_table_delete_entities(world, table); + } +} + +static +void delete_tables_for_id_record( + ecs_world_t *world, + ecs_id_t id, + ecs_id_record_t *idr) +{ + /* Delete tables in id record. Because deleting the table updates the + * map, remove the map pointer from the id record. This will prevent the + * table from removing itself from the map as it is deleted, which + * allows for iterating the map without changing it. */ + + if (!world->is_fini) { + ecs_map_t *table_index = idr->table_index; + idr->table_index = NULL; + ecs_map_iter_t it = ecs_map_iter(table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + flecs_delete_table(world, tr->table); + } + ecs_map_free(table_index); + + flecs_clear_id_record(world, id); + } +} + +static +void on_delete_object_action( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (idr) { + ecs_map_t *table_index = idr->table_index; + ecs_map_iter_t it = ecs_map_iter(table_index); + ecs_table_record_t *tr; + + /* Execute the on delete action */ + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + ecs_table_t *table = tr->table; + + if (!ecs_table_count(table)) { + continue; + } + + ecs_id_t *rel_id = ecs_vector_get(table->type, ecs_id_t, tr->column); + ecs_assert(rel_id != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t rel = ECS_PAIR_RELATION(*rel_id); + /* delete_object_action should be invoked for relations */ + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + + /* Get the record for the relation, to find the delete action */ + ecs_id_record_t *idrr = flecs_get_id_record(world, rel); + if (idrr) { + ecs_entity_t action = idrr->on_delete_object; + if (!action || action == EcsRemove) { + remove_from_table(world, table, id, tr->column, tr->count); + it = ecs_map_iter(table_index); + } else if (action == EcsDelete) { + delete_objects(world, table); + } else if (action == EcsThrow) { + throw_invalid_delete(world, id); + } + } else { + /* If no record was found for the relation, assume the default + * action which is to remove the relationship */ + remove_from_table(world, table, id, tr->column, tr->count); + it = ecs_map_iter(table_index); + } + } + + delete_tables_for_id_record(world, id, idr); + } +} + +static +void on_delete_relation_action( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *idr = flecs_get_id_record(world, id); + + char buf[255]; ecs_id_str(world, id, buf, 255); + + if (idr) { + ecs_entity_t on_delete = idr->on_delete; + if (on_delete == EcsThrow) { + throw_invalid_delete(world, id); + } + + ecs_map_t *table_index = idr->table_index; + ecs_map_iter_t it = ecs_map_iter(table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + ecs_table_t *table = tr->table; + ecs_entity_t action = idr->on_delete; + + if (!action || action == EcsRemove) { + remove_from_table(world, table, id, tr->column, tr->count); + } else if (action == EcsDelete) { + delete_objects(world, table); + } + } + + delete_tables_for_id_record(world, id, idr); + } +} + +static +void on_delete_action( + ecs_world_t *world, + ecs_entity_t entity) +{ + on_delete_relation_action(world, entity); + on_delete_relation_action(world, ecs_pair(entity, EcsWildcard)); + on_delete_object_action(world, ecs_pair(EcsWildcard, entity)); +} + +void ecs_delete_children( + ecs_world_t *world, + ecs_entity_t parent) +{ + on_delete_action(world, parent); +} + +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(world, stage, entity)) { + return; + } + + ecs_record_t *r = flecs_sparse_get( + world->store.entity_index, ecs_record_t, entity); + if (r) { + ecs_entity_info_t info = {0}; + set_info_from_record(&info, r); + + ecs_table_t *table = info.table; + uint64_t table_id = 0; + if (table) { + table_id = table->id; + } + + if (info.is_watched) { + /* Make row positive which prevents infinite recursion in case + * of cyclic delete actions */ + r->row = (-r->row); + + /* Ensure that the store contains no dangling references to the + * deleted entity (as a component, or as part of a relation) */ + on_delete_action(world, entity); + + /* Refetch data. In case of circular relations, the entity may have + * moved to a different table. */ + set_info_from_record(&info, r); + + table = info.table; + if (table) { + table_id = table->id; + } else { + table_id = 0; + } + + if (r->table) { + ecs_ids_t to_remove = flecs_type_to_ids(r->table->type); + update_component_monitors(world, entity, NULL, &to_remove); + } + } + + ecs_assert(!table_id || table, ECS_INTERNAL_ERROR, NULL); + + /* If entity has components, remove them. Check if table is still alive, + * as delete actions could have deleted the table already. */ + if (table_id && flecs_sparse_is_alive(world->store.tables, table_id)) { + ecs_type_t type = table->type; + ecs_ids_t to_remove = flecs_type_to_ids(type); + delete_entity(world, table, info.data, info.row, &to_remove); + r->table = NULL; + } + + r->row = 0; + + /* Remove (and invalidate) entity after executing handlers */ + flecs_sparse_remove(world->store.entity_index, entity); + } + + flecs_defer_flush(world, stage); +} + +void ecs_add_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = flecs_type_to_ids(type); + add_ids(world, entity, &components); +} + +void ecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = { .array = &id, .count = 1 }; + add_ids(world, entity, &components); +} + +void ecs_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = flecs_type_to_ids(type); + remove_ids(world, entity, &components); +} + +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components = { .array = &id, .count = 1 }; + remove_ids(world, entity, &components); +} + +// DEPRECATED +void ecs_add_remove_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id_add, + ecs_id_t id_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id_add), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id_remove), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components_add = { .array = &id_add, .count = 1 }; + ecs_ids_t components_remove = { .array = &id_remove, .count = 1 }; + add_remove(world, entity, &components_add, &components_remove); +} + +void ecs_add_remove_type( + ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t to_add, + ecs_type_t to_remove) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + ecs_ids_t components_add = flecs_type_to_ids(to_add); + ecs_ids_t components_remove = flecs_type_to_ids(to_remove); + add_remove(world, entity, &components_add, &components_remove); +} + +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(src != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, src), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (!dst) { + dst = ecs_new_id(world); + } + + if (flecs_defer_clone(world, stage, dst, src, copy_value)) { + return dst; + } + + ecs_entity_info_t src_info; + bool found = flecs_get_info(world, src, &src_info); + ecs_table_t *src_table = src_info.table; + + if (!found || !src_table) { + return dst; + } + + ecs_type_t src_type = src_table->type; + ecs_ids_t to_add = flecs_type_to_ids(src_type); + + ecs_entity_info_t dst_info = {0}; + dst_info.row = new_entity(world, dst, &dst_info, src_table, &to_add, true); + + if (copy_value) { + flecs_table_move(world, dst, src, src_table, dst_info.data, + dst_info.row, src_table, src_info.data, src_info.row, true); + + flecs_run_set_systems(world, 0, + src_table, src_info.data, NULL, dst_info.row, 1, true); + } + + flecs_defer_flush(world, stage); + + return dst; +} + +const void* ecs_get_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(flecs_stage_from_readonly_world(world)->asynchronous == false, + ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = ecs_eis_get(world, entity); + if (!r) { + return NULL; + } + + ecs_table_t *table = r->table; + if (!table) { + return NULL; + } + + ecs_id_record_t *idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + ecs_table_record_t *tr = ecs_map_get(idr->table_index, + ecs_table_record_t, table->id); + if (!tr) { + return get_base_component(world, table, id, idr->table_index, NULL, 0); + } + + bool is_monitored; + int32_t row = flecs_record_to_row(r->row, &is_monitored); + + return get_component_w_index(table, tr->column, row); +} + +const void* ecs_get_ref_w_id( + const ecs_world_t * world, + ecs_ref_t * ref, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!entity || !ref->entity || entity == ref->entity, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!id || !ref->component || id == ref->component, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *record = ref->record; + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + entity |= ref->entity; + + if (!record) { + record = ecs_eis_get(world, entity); + } + + if (!record || !record->table) { + return NULL; + } + + ecs_table_t *table = record->table; + + if (ref->record == record && + ref->table == table && + ref->row == record->row && + ref->alloc_count == table->alloc_count) + { + return ref->ptr; + } + + id |= ref->component; + + int32_t row = record->row; + + ref->entity = entity; + ref->component = id; + ref->table = table; + ref->row = record->row; + ref->alloc_count = table->alloc_count; + + if (table) { + bool is_monitored; + row = flecs_record_to_row(row, &is_monitored); + ref->ptr = get_component(world, table, row, id); + } + + ref->record = record; + + return ref->ptr; +} + +void* ecs_get_mut_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool * is_added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + void *result; + + if (flecs_defer_set( + world, stage, EcsOpMut, entity, id, 0, NULL, &result, is_added)) + { + return result; + } + + ecs_entity_info_t info; + result = get_mutable(world, entity, id, &info, is_added); + + /* Store table so we can quickly check if returned pointer is still valid */ + ecs_table_t *table = info.record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep track of alloc count of table, since even if the entity has not + * moved, other entities could have been added to the table which could + * reallocate arrays. Also store the row, as the entity could have + * reallocated. */ + int32_t alloc_count = table->alloc_count; + int32_t row = info.record->row; + + flecs_defer_flush(world, stage); + + /* Ensure that after flushing, the pointer is still valid. Flushing may + * trigger callbacks, which could do anything with the entity */ + if (table != info.record->table || + alloc_count != info.record->table->alloc_count || + row != info.record->row) + { + if (flecs_get_info(world, entity, &info) && info.table) { + result = get_component(world, info.table, info.row, id); + } else { + /* A trigger has removed the component we just added. This is not + * allowed, an application should always be able to assume that + * get_mut returns a valid pointer. */ + ecs_assert(false, ECS_INVALID_OPERATION, NULL); + } + } + + return result; +} + +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + void *result; + + if (flecs_defer_set( + world, stage, EcsOpMut, entity, id, 0, NULL, &result, NULL)) + { + return result; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_ids_t to_add = { + .array = &id, + .count = 1 + }; + + add_ids_w_info(world, entity, &info, &to_add, + false /* Add component without constructing it */ ); + + void *ptr = get_component(world, info.table, info.row, id); + + flecs_defer_flush(world, stage); + + return ptr; +} + +void ecs_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_modified(world, 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_assert(ecs_has_id(world, entity, id), + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_info_t info = {0}; + if (flecs_get_info(world, entity, &info)) { + ecs_column_t *column = ecs_table_column_for_id(world, info.table, id); + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_run_set_systems(world, id, + info.table, info.data, column, info.row, 1, false); + } + + flecs_table_mark_dirty(info.table, id); + + flecs_defer_flush(world, stage); +} + +static +ecs_entity_t assign_ptr_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void * ptr, + bool is_move, + bool notify) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (!entity) { + entity = ecs_new_id(world); + ecs_entity_t scope = stage->scope; + if (scope) { + ecs_add_pair(world, entity, EcsChildOf, scope); + } + } + + if (flecs_defer_set(world, stage, EcsOpSet, entity, id, + flecs_from_size_t(size), ptr, NULL, NULL)) + { + return entity; + } + + ecs_entity_info_t info; + + void *dst = get_mutable(world, entity, id, &info, NULL); + + /* This can no longer happen since we defer operations */ + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ptr) { + ecs_entity_t real_id = ecs_get_typeid(world, id); + const ecs_type_info_t *cdata = get_c_info(world, real_id); + if (cdata) { + if (is_move) { + ecs_move_t move = cdata->lifecycle.move; + if (move) { + move(world, real_id, &entity, &entity, dst, ptr, size, 1, + cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, ptr, flecs_from_size_t(size)); + } + } else { + ecs_copy_t copy = cdata->lifecycle.copy; + if (copy) { + copy(world, real_id, &entity, &entity, dst, ptr, size, 1, + cdata->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, ptr, flecs_from_size_t(size)); + } + } + } else { + ecs_os_memcpy(dst, ptr, flecs_from_size_t(size)); + } + } else { + memset(dst, 0, size); + } + + flecs_table_mark_dirty(info.table, id); + + if (notify) { + ecs_column_t *column = ecs_table_column_for_id(world, info.table, id); + flecs_run_set_systems(world, id, + info.table, info.data, column, info.row, 1, false); + } + + flecs_defer_flush(world, stage); + + return entity; +} + +ecs_entity_t ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!entity || ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + /* Safe to cast away const: function won't modify if move arg is false */ + return assign_ptr_w_id( + world, entity, id, size, (void*)ptr, false, true); +} + +ecs_entity_t ecs_get_case( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t sw_id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, sw_id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_entity_info_t info; + ecs_table_t *table; + if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + return 0; + } + + sw_id = sw_id | ECS_SWITCH; + + ecs_type_t type = table->type; + int32_t index = ecs_type_index_of(type, 0, sw_id); + if (index == -1) { + return 0; + } + + index -= table->sw_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = info.data->sw_columns[index].data; + return flecs_switch_get(sw, info.row); +} + +void ecs_enable_component_w_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_enable( + world, stage, entity, id, enable)) + { + return; + } else { + /* Operations invoked by enable/disable should not be deferred */ + stage->defer --; + } + + ecs_entity_info_t info; + flecs_get_info(world, entity, &info); + + ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; + + ecs_table_t *table = info.table; + int32_t index = -1; + if (table) { + index = ecs_type_index_of(table->type, 0, bs_id); + } + + if (index == -1) { + ecs_add_id(world, entity, bs_id); + ecs_enable_component_w_id(world, entity, id, enable); + return; + } + + index -= table->bs_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &info.data->bs_columns[index].data; + ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_bitset_set(bs, info.row, enable); +} + +bool ecs_is_component_enabled_w_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_info_t info; + ecs_table_t *table; + if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + return false; + } + + ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; + + ecs_type_t type = table->type; + int32_t index = ecs_type_index_of(type, 0, bs_id); + if (index == -1) { + /* If table does not have DISABLED column for component, component is + * always enabled, if the entity has it */ + return ecs_has_id(world, entity, id); + } + + index -= table->bs_column_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Data cannot be NULl, since entity is stored in the table */ + ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &info.data->bs_columns[index].data; + + return flecs_bitset_get(bs, info.row); +} + +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + if (ECS_HAS_ROLE(id, CASE)) { + ecs_entity_info_t info; + ecs_table_t *table; + if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { + return false; + } + + int32_t index = flecs_table_switch_from_case(world, table, id); + ecs_assert(index < table->sw_column_count, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = info.data; + ecs_switch_t *sw = data->sw_columns[index].data; + ecs_entity_t value = flecs_switch_get(sw, info.row); + + return value == (id & ECS_COMPONENT_MASK); + } else { + ecs_table_t *table = ecs_get_table(world, entity); + if (!table) { + return false; + } + + return ecs_type_match( + world, table, table->type, 0, id, EcsIsA, 0, 0, NULL) != -1; + } +} + +bool ecs_has_type( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + return has_type(world, entity, type, true, true); +} + +ecs_entity_t ecs_get_object( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(rel != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + if (!entity) { + return 0; + } + + ecs_table_t *table = ecs_get_table(world, entity); + if (!table) { + return 0; + } + + ecs_id_t wc = ecs_pair(rel, EcsWildcard); + ecs_table_record_t *tr = flecs_get_table_record(world, table, wc); + if (!tr) { + return 0; + } + + if (index >= tr->count) { + return 0; + } + + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + return ecs_pair_object(world, ids[tr->column + index]); +} + +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, EcsName); + + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +const char* ecs_get_symbol( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, EcsSymbol); + + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} + +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + entity = ecs_new_id(world); + } + + ecs_set_pair(world, entity, EcsIdentifier, EcsName, {.value = (char*)name}); + + return entity; +} + +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + entity = ecs_new_id(world); + } + + ecs_set_pair(world, entity, EcsIdentifier, EcsSymbol, { + .value = (char*)name + }); + + return entity; +} + +ecs_type_t ecs_type_from_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + if (!id) { + return NULL; + } + + if (!(id & ECS_ROLE_MASK)) { + const EcsType *type = ecs_get(world, id, EcsType); + if (type) { + return type->normalized; + } + } + + ecs_ids_t ids = { + .array = &id, + .count = 1 + }; + + ecs_table_t *table = flecs_table_find_or_create(world, &ids); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +ecs_id_t ecs_type_to_id( + const ecs_world_t *world, + ecs_type_t type) +{ + (void)world; + + if (!type) { + return 0; + } + + /* If array contains n entities, it cannot be reduced to a single entity */ + if (ecs_vector_count(type) != 1) { + ecs_abort(ECS_TYPE_NOT_AN_ENTITY, NULL); + } + + return *(ecs_vector_first(type, ecs_id_t)); +} + +ecs_id_t ecs_make_pair( + ecs_entity_t relation, + ecs_entity_t object) +{ + return ecs_pair(relation, object); +} + +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* 0 is not a valid entity id */ + if (!entity) { + return false; + } + + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return false; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + /* When checking roles and/or pairs, the generation count may have been + * stripped away. Just test if the entity is 0 or not. */ + if (ECS_HAS_ROLE(entity, PAIR)) { + ecs_entity_t lo = ECS_PAIR_OBJECT(entity); + ecs_entity_t hi = ECS_PAIR_RELATION(entity); + return lo != 0 && hi != 0; + } else + if (entity & ECS_ROLE) { + return ecs_entity_t_lo(entity) != 0; + } + + /* An id may not yet exist in the world which does not mean it cannot be + * used as an entity identifier. An example is when a hard-coded entity id + * is used. However, if the entity id does exist in the world, it must be + * alive. */ + return !ecs_exists(world, entity) || ecs_is_alive(world, entity); +} + +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + return ecs_eis_is_alive(world, entity); +} + +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + if (ecs_is_alive(world, entity)) { + return entity; + } + + /* Make sure id does not have generation. This guards against accidentally + * "upcasting" a not alive identifier to a alive one. */ + ecs_assert((uint32_t)entity == entity, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_t current = ecs_eis_get_current(world, entity); + if (!current || !ecs_is_alive(world, current)) { + return 0; + } + + return current; +} + +void ecs_ensure( + ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + if (ecs_eis_is_alive(world, entity)) { + /* Nothing to be done, already alive */ + return; + } + + /* Ensure id exists. The underlying datastructure will verify that the + * generation count matches the provided one. */ + ecs_eis_ensure(world, entity); +} + +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + return ecs_eis_exists(world, entity); +} + +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_record_t *record = ecs_eis_get(world, entity); + ecs_table_t *table; + if (record && (table = record->table)) { + return table; + } + + return NULL; +} + +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; +} + +ecs_id_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + if (ECS_HAS_ROLE(id, PAIR)) { + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_entity_t rel = ecs_get_alive(world, ECS_PAIR_RELATION(id)); + + /* If relation is marked as a tag, it never has data. Return relation */ + if (ecs_has_id(world, rel, EcsTag)) { + return 0; + } + + const EcsComponent *ptr = ecs_get(world, rel, EcsComponent); + if (ptr && ptr->size != 0) { + return rel; + } else { + ecs_entity_t obj = ecs_get_alive(world, ECS_PAIR_OBJECT(id)); + ptr = ecs_get(world, obj, EcsComponent); + + if (ptr && ptr->size != 0) { + return obj; + } + + /* Neither relation nor object have data */ + return 0; + } + + } else if (id & ECS_ROLE_MASK) { + return 0; + } + + return id; +} + +int32_t ecs_count_type( + const ecs_world_t *world, + ecs_type_t type) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!type) { + return 0; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + return ecs_count_filter(world, &(ecs_filter_t){ + .include = type + }); +} + +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return 0; + } + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + /* Get temporary type that just contains entity */ + ECS_VECTOR_STACK(type, ecs_entity_t, &entity, 1); + + return ecs_count_filter(world, &(ecs_filter_t){ + .include = type + }); +} + +int32_t ecs_count_filter( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + int32_t result = 0; + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + if (!filter || flecs_table_match_filter(world, table, filter)) { + result += ecs_table_count(table); + } + } + + return result; +} + +bool ecs_defer_begin( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_none(world, stage); +} + +bool ecs_defer_end( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_flush(world, stage); +} + +static +size_t append_to_str( + char **buffer, + const char *str, + size_t bytes_left, + size_t *required) +{ + char *ptr = NULL; + if (buffer) { + ptr = *buffer; + } + + size_t len = strlen(str); + size_t to_write; + if (bytes_left < len) { + to_write = bytes_left; + bytes_left = 0; + } else { + to_write = len; + bytes_left -= len; + } + + if (to_write && ptr) { + ecs_os_memcpy(ptr, str, to_write); + } + + (*required) += len; + + if (buffer) { + (*buffer) += to_write; + } + + return bytes_left; +} + +const char* ecs_role_str( + ecs_entity_t entity) +{ + if (ECS_HAS_ROLE(entity, PAIR)) { + return "PAIR"; + } else + if (ECS_HAS_ROLE(entity, DISABLED)) { + return "DISABLED"; + } else + if (ECS_HAS_ROLE(entity, XOR)) { + return "XOR"; + } else + if (ECS_HAS_ROLE(entity, OR)) { + return "OR"; + } else + if (ECS_HAS_ROLE(entity, AND)) { + return "AND"; + } else + if (ECS_HAS_ROLE(entity, NOT)) { + return "NOT"; + } else + if (ECS_HAS_ROLE(entity, SWITCH)) { + return "SWITCH"; + } else + if (ECS_HAS_ROLE(entity, CASE)) { + return "CASE"; + } else + if (ECS_HAS_ROLE(entity, OWNED)) { + return "OWNED"; + } else { + return "UNKNOWN"; + } +} + +size_t ecs_id_str( + const ecs_world_t *world, + ecs_id_t id, + char *buffer, + size_t buffer_len) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + char *ptr = buffer; + char **pptr = NULL; + if (ptr) { + pptr = &ptr; + } + + size_t bytes_left = buffer_len - 1, required = 0; + if (id & ECS_ROLE_MASK && !ECS_HAS_ROLE(id, PAIR)) { + const char *role = ecs_role_str(id); + bytes_left = append_to_str(pptr, role, bytes_left, &required); + bytes_left = append_to_str(pptr, "|", bytes_left, &required); + } + + ecs_entity_t e = id & ECS_COMPONENT_MASK; + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t lo = ECS_PAIR_OBJECT(id); + ecs_entity_t hi = ECS_PAIR_RELATION(id); + + if (lo) lo = ecs_get_alive(world, lo); + if (hi) hi = ecs_get_alive(world, hi); + + if (hi) { + char *hi_path = ecs_get_fullpath(world, hi); + bytes_left = append_to_str(pptr, "(", bytes_left, &required); + bytes_left = append_to_str(pptr, hi_path, bytes_left, &required); + ecs_os_free(hi_path); + bytes_left = append_to_str(pptr, ",", bytes_left, &required); + } + + char *lo_path = ecs_get_fullpath(world, lo); + bytes_left = append_to_str(pptr, lo_path, bytes_left, &required); + ecs_os_free(lo_path); + + if (hi) { + append_to_str(pptr, ")", bytes_left, &required); + } + } else { + char *path = ecs_get_fullpath(world, e); + append_to_str(pptr, path, bytes_left, &required); + ecs_os_free(path); + } + + if (ptr) { + ptr[0] = '\0'; + } + + return required; +} + +static +void flush_bulk_new( + ecs_world_t * world, + ecs_op_t * op) +{ + ecs_entity_t *ids = op->is._n.entities; + void **bulk_data = op->is._n.bulk_data; + if (bulk_data) { + ecs_entity_t *components = op->components.array; + int c, c_count = op->components.count; + for (c = 0; c < c_count; c ++) { + ecs_entity_t component = components[c]; + const EcsComponent *cptr = flecs_component_from_id(world, component); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + size_t size = flecs_to_size_t(cptr->size); + void *ptr, *data = bulk_data[c]; + int i, count = op->is._n.count; + for (i = 0, ptr = data; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + assign_ptr_w_id(world, ids[i], component, size, ptr, + true, true); + } + ecs_os_free(data); + } + ecs_os_free(bulk_data); + } else { + int i, count = op->is._n.count; + for (i = 0; i < count; i ++) { + add_ids(world, ids[i], &op->components); + } + } + + if (op->components.count > 1) { + ecs_os_free(op->components.array); + } + + ecs_os_free(ids); +} + +static +void free_value( + ecs_world_t *world, + ecs_entity_t *entities, + ecs_id_t id, + void *value, + int32_t count) +{ + ecs_entity_t real_id = ecs_get_typeid(world, id); + const ecs_type_info_t *info = flecs_get_c_info(world, real_id); + ecs_xtor_t dtor; + + if (info && (dtor = info->lifecycle.dtor)) { + ecs_size_t size = info->size; + void *ptr; + int i; + for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { + dtor(world, id, &entities[i], ptr, flecs_to_size_t(size), 1, + info->lifecycle.ctx); + } + } +} + +static +void discard_op( + ecs_world_t *world, + ecs_op_t * op) +{ + if (op->kind == EcsOpBulkNew) { + void **bulk_data = op->is._n.bulk_data; + if (bulk_data) { + ecs_entity_t *entities = op->is._n.entities; + ecs_entity_t *components = op->components.array; + int c, c_count = op->components.count; + for (c = 0; c < c_count; c ++) { + free_value(world, entities, components[c], bulk_data[c], + op->is._n.count); + ecs_os_free(bulk_data[c]); + } + } + } else { + void *value = op->is._1.value; + if (value) { + free_value(world, &op->is._1.entity, op->component, op->is._1.value, 1); + ecs_os_free(value); + } + } + + ecs_entity_t *components = op->components.array; + if (components) { + ecs_os_free(components); + } +} + +static +bool is_entity_valid( + ecs_world_t *world, + ecs_entity_t e) +{ + if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { + return false; + } + return true; +} + +static +bool remove_invalid( + ecs_world_t * world, + ecs_ids_t * ids) +{ + ecs_entity_t *array = ids->array; + int32_t i, offset = 0, count = ids->count; + + for (i = 0; i < count; i ++) { + ecs_id_t id = array[i]; + bool is_remove = false; + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t rel = ecs_pair_relation(world, id); + if (!rel || !is_entity_valid(world, rel)) { + /* After relation is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + is_remove = true; + } else { + ecs_entity_t obj = ecs_pair_object(world, id); + if (!obj || !is_entity_valid(world, obj)) { + /* Check the relation's policy for deleted objects */ + ecs_id_record_t *idr = flecs_get_id_record(world, rel); + if (!idr || (idr->on_delete_object == EcsRemove)) { + is_remove = true; + } else { + if (idr->on_delete_object == EcsDelete) { + /* Entity should be deleted, don't bother checking + * other ids */ + return false; + } else if (idr->on_delete_object == EcsThrow) { + /* If policy is throw this object should not have + * been deleted */ + throw_invalid_delete(world, id); + } + } + } + } + + } else { + id &= ECS_COMPONENT_MASK; + + if (!is_entity_valid(world, id)) { + /* After relation is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + is_remove = true; + } + } + + if (is_remove) { + offset ++; + count --; + } + + ids->array[i] = ids->array[i + offset]; + } + + ids->count = count; + + return true; +} + +/* Leave safe section. Run all deferred commands. */ +bool flecs_defer_flush( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!--stage->defer) { + /* Set to NULL. Processing deferred commands can cause additional + * commands to get enqueued (as result of reactive systems). Make sure + * that the original array is not reallocated, as this would complicate + * processing the queue. */ + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + + if (defer_queue) { + ecs_op_t *ops = ecs_vector_first(defer_queue, ecs_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + + for (i = 0; i < count; i ++) { + ecs_op_t *op = &ops[i]; + ecs_entity_t e = op->is._1.entity; + if (op->kind == EcsOpBulkNew) { + e = 0; + } + + /* If entity is no longer alive, this could be because the queue + * contained both a delete and a subsequent add/remove/set which + * should be ignored. */ + if (e && !ecs_is_alive(world, e) && ecs_eis_exists(world, e)) { + ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone, + ECS_INTERNAL_ERROR, NULL); + world->discard_count ++; + discard_op(world, op); + continue; + } + + if (op->components.count == 1) { + op->components.array = &op->component; + } + + switch(op->kind) { + case EcsOpNew: + case EcsOpAdd: + if (remove_invalid(world, &op->components)) { + world->add_count ++; + add_ids(world, e, &op->components); + } else { + ecs_delete(world, e); + } + break; + case EcsOpRemove: + remove_ids(world, e, &op->components); + break; + case EcsOpClone: + ecs_clone(world, e, op->component, op->is._1.clone_value); + break; + case EcsOpSet: + assign_ptr_w_id(world, e, + op->component, flecs_to_size_t(op->is._1.size), + op->is._1.value, true, true); + break; + case EcsOpMut: + assign_ptr_w_id(world, e, + op->component, flecs_to_size_t(op->is._1.size), + op->is._1.value, true, false); + break; + case EcsOpModified: + ecs_modified_id(world, e, op->component); + break; + case EcsOpDelete: { + ecs_delete(world, e); + break; + } + case EcsOpEnable: + ecs_enable_component_w_id( + world, e, op->component, true); + break; + case EcsOpDisable: + ecs_enable_component_w_id( + world, e, op->component, false); + break; + case EcsOpClear: + ecs_clear(world, e); + break; + case EcsOpBulkNew: + flush_bulk_new(world, op); + + /* Continue since flush_bulk_new is repsonsible for cleaning + * up resources. */ + continue; + } + + if (op->components.count > 1) { + ecs_os_free(op->components.array); + } + + if (op->is._1.value) { + ecs_os_free(op->is._1.value); + } + } + + if (stage->defer_queue) { + ecs_vector_free(stage->defer_queue); + } + + /* Restore defer queue */ + ecs_vector_clear(defer_queue); + stage->defer_queue = defer_queue; + } + + return true; + } + + return false; +} + +/* Delete operations from queue without executing them. */ +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!--stage->defer) { + ecs_vector_t *defer_queue = stage->defer_queue; + stage->defer_queue = NULL; + + if (defer_queue) { + ecs_op_t *ops = ecs_vector_first(defer_queue, ecs_op_t); + int32_t i, count = ecs_vector_count(defer_queue); + for (i = 0; i < count; i ++) { + discard_op(world, &ops[i]); + } + + if (stage->defer_queue) { + ecs_vector_free(stage->defer_queue); + } + + /* Restore defer queue */ + ecs_vector_clear(defer_queue); + stage->defer_queue = defer_queue; + } + + return true; + } + + return false; +} diff --git a/fggl/ecs2/flecs/src/entity_index.h b/fggl/ecs2/flecs/src/entity_index.h new file mode 100644 index 0000000000000000000000000000000000000000..3d6e69122777754406b0e65f87756d0097e5ff0a --- /dev/null +++ b/fggl/ecs2/flecs/src/entity_index.h @@ -0,0 +1,39 @@ +/** + * @file entity_index.h + * @brief Entity index data structure. + * + * The entity index stores the table, row for an entity id. It is implemented as + * a sparse set. This file contains convenience macro's for working with the + * entity index. + */ + +#ifndef FLECS_ENTITY_INDEX_H +#define FLECS_ENTITY_INDEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ecs_eis_get(world, entity) flecs_sparse_get((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_get_any(world, entity) flecs_sparse_get_any((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_set(world, entity, ...) (flecs_sparse_set((world->store).entity_index, ecs_record_t, entity, (__VA_ARGS__))) +#define ecs_eis_ensure(world, entity) flecs_sparse_ensure((world->store).entity_index, ecs_record_t, entity) +#define ecs_eis_delete(world, entity) flecs_sparse_remove((world->store).entity_index, entity) +#define ecs_eis_set_generation(world, entity) flecs_sparse_set_generation((world->store).entity_index, entity) +#define ecs_eis_is_alive(world, entity) flecs_sparse_is_alive((world->store).entity_index, entity) +#define ecs_eis_get_current(world, entity) flecs_sparse_get_alive((world->store).entity_index, entity) +#define ecs_eis_exists(world, entity) flecs_sparse_exists((world->store).entity_index, entity) +#define ecs_eis_recycle(world) flecs_sparse_new_id((world->store).entity_index) +#define ecs_eis_clear_entity(world, entity, is_watched) ecs_eis_set((world->store).entity_index, entity, &(ecs_record_t){NULL, is_watched}) +#define ecs_eis_set_size(world, size) flecs_sparse_set_size((world->store).entity_index, size) +#define ecs_eis_count(world) flecs_sparse_count((world->store).entity_index) +#define ecs_eis_clear(world) flecs_sparse_clear((world->store).entity_index) +#define ecs_eis_copy(world) flecs_sparse_copy((world->store).entity_index) +#define ecs_eis_free(world) flecs_sparse_free((world->store).entity_index) +#define ecs_eis_memory(world, allocd, used) flecs_sparse_memory((world->store).entity_index, allocd, used) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/src/filter.c b/fggl/ecs2/flecs/src/filter.c new file mode 100644 index 0000000000000000000000000000000000000000..56134aecffe2ce3da812d4ba86fea59e44fa9835 --- /dev/null +++ b/fggl/ecs2/flecs/src/filter.c @@ -0,0 +1,1170 @@ + +#include "private_api.h" + +static +int resolve_identifier( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_id_t *identifier) +{ + if (!identifier->name) { + return 0; + } + + if (identifier->var == EcsVarDefault) { + if (ecs_identifier_is_var(identifier->name)) { + identifier->var = EcsVarIsVariable; + } + } + + if (identifier->var != EcsVarIsVariable) { + if (ecs_identifier_is_0(identifier->name)) { + identifier->entity = 0; + } else { + ecs_entity_t e = ecs_lookup_symbol(world, identifier->name, true); + if (!e) { + ecs_parser_error(name, expr, 0, + "unresolved identifier '%s'", identifier->name); + return -1; + } + + /* Use OR, as entity may have already been populated with role */ + identifier->entity = e; + } + } + + return 0; +} + +bool ecs_identifier_is_0( + const char *id) +{ + return id[0] == '0' && !id[1]; +} + +bool ecs_identifier_is_var( + const char *id) +{ + if (id[0] == '_') { + return true; + } + + if (isdigit(id[0])) { + return false; + } + + const char *ptr; + char ch; + for (ptr = id; (ch = *ptr); ptr ++) { + if (!isupper(ch) && ch != '_' && !isdigit(ch)) { + return false; + } + } + + return true; +} + +static +int term_resolve_ids( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term) +{ + if (resolve_identifier(world, name, expr, &term->pred)) { + return -1; + } + if (resolve_identifier(world, name, expr, &term->args[0])) { + return -1; + } + if (resolve_identifier(world, name, expr, &term->args[1])) { + return -1; + } + + if (term->args[1].entity || term->role == ECS_PAIR) { + /* Both the relation and object must be set */ + ecs_assert(term->pred.entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term->args[1].entity != 0, ECS_INVALID_PARAMETER, NULL); + term->id = ecs_pair(term->pred.entity, term->args[1].entity); + } else { + term->id = term->pred.entity; + } + + return 0; +} + +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) +{ + if (id == pattern) { + return true; + } + + if (ECS_HAS_ROLE(pattern, PAIR)) { + if (!ECS_HAS_ROLE(id, PAIR)) { + return false; + } + + ecs_entity_t id_rel = ECS_PAIR_RELATION(id); + ecs_entity_t id_obj = ECS_PAIR_OBJECT(id); + ecs_entity_t pattern_rel = ECS_PAIR_RELATION(pattern); + ecs_entity_t pattern_obj = ECS_PAIR_OBJECT(pattern); + + ecs_assert(id_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(id_obj != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_assert(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); + + if (pattern_rel == EcsWildcard) { + if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { + return true; + } + } else if (pattern_obj == EcsWildcard) { + if (pattern_rel == id_rel) { + return true; + } + } + } else { + if ((id & ECS_ROLE_MASK) != (pattern & ECS_ROLE_MASK)) { + return false; + } + + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; + } + } + + return false; +} + +bool ecs_id_is_pair( + ecs_id_t id) +{ + return ECS_HAS_ROLE(id, PAIR); +} + +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if (id == EcsWildcard) { + return true; + } else if (ECS_HAS_ROLE(id, PAIR)) { + return ECS_PAIR_RELATION(id) == EcsWildcard || + ECS_PAIR_OBJECT(id) == EcsWildcard; + } + + return false; +} + +bool ecs_term_id_is_set( + const ecs_term_id_t *id) +{ + return id->entity != 0 || id->name != NULL; +} + +bool ecs_term_is_initialized( + const ecs_term_t *term) +{ + return term->id != 0 || ecs_term_id_is_set(&term->pred); +} + +bool ecs_term_is_trivial( + const ecs_term_t *term) +{ + if (term->inout != EcsInOutDefault) { + return false; + } + + if (term->args[0].entity != EcsThis) { + return false; + } + + if (term->args[0].set.mask != EcsDefaultSet) { + return false; + } + + if (term->oper != EcsAnd && term->oper != EcsAndFrom) { + return false; + } + + if (term->name != NULL) { + return false; + } + + return true; +} + +int ecs_term_finalize( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_term_t *term) +{ + if (term->id) { + /* Allow for combining explicit object with id */ + if (term->args[1].name && !term->args[1].entity) { + if (resolve_identifier(world, name, expr, &term->args[1])) { + return -1; + } + } + + /* If other fields are set, make sure they are consistent with id */ + if (term->args[1].entity) { + ecs_assert(term->pred.entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term->id == + ecs_pair(term->pred.entity, term->args[1].entity), + ECS_INVALID_PARAMETER, NULL); + } else if (term->pred.entity) { + /* If only predicate is set (not object) it must match the id + * without any roles set. */ + ecs_assert(term->pred.entity == (term->id & ECS_COMPONENT_MASK), + ECS_INVALID_PARAMETER, NULL); + } + + /* If id is set, check for pair and derive predicate and object */ + if (ECS_HAS_ROLE(term->id, PAIR)) { + term->pred.entity = ECS_PAIR_RELATION(term->id); + term->args[1].entity = ECS_PAIR_OBJECT(term->id); + } else { + term->pred.entity = term->id & ECS_COMPONENT_MASK; + } + + if (!term->role) { + term->role = term->id & ECS_ROLE_MASK; + } else { + /* If id already has a role set it should be equal to the provided + * role */ + ecs_assert(!(term->id & ECS_ROLE_MASK) || + (term->id & ECS_ROLE_MASK) == term->role, + ECS_INVALID_PARAMETER, NULL); + } + } else { + if (term_resolve_ids(world, name, expr, term)) { + /* One or more identifiers could not be resolved */ + return -1; + } + } + + /* role field should only set role bits */ + ecs_assert(term->role == (term->role & ECS_ROLE_MASK), + ECS_INVALID_PARAMETER, NULL); + + term->id |= term->role; + + if (!term->args[0].entity && + term->args[0].set.mask != EcsNothing && + term->args[0].var != EcsVarIsVariable) + { + term->args[0].entity = EcsThis; + } + + if (term->args[0].set.mask & (EcsSuperSet | EcsSubSet)) { + if (!term->args[0].set.relation) { + term->args[0].set.relation = EcsIsA; + } + } + + return 0; +} + +ecs_term_t ecs_term_copy( + const ecs_term_t *src) +{ + ecs_term_t dst = *src; + dst.name = ecs_os_strdup(src->name); + dst.pred.name = ecs_os_strdup(src->pred.name); + dst.args[0].name = ecs_os_strdup(src->args[0].name); + dst.args[1].name = ecs_os_strdup(src->args[1].name); + return dst; +} + +ecs_term_t ecs_term_move( + ecs_term_t *src) +{ + if (src->move) { + ecs_term_t dst = *src; + src->name = NULL; + src->pred.name = NULL; + src->args[0].name = NULL; + src->args[1].name = NULL; + return dst; + } else { + return ecs_term_copy(src); + } +} + +void ecs_term_fini( + ecs_term_t *term) +{ + ecs_os_free(term->pred.name); + ecs_os_free(term->args[0].name); + ecs_os_free(term->args[1].name); + ecs_os_free(term->name); +} + +int ecs_filter_finalize( + const ecs_world_t *world, + ecs_filter_t *f) +{ + int32_t i, term_count = f->term_count, actual_count = 0; + ecs_term_t *terms = f->terms; + bool is_or = false, prev_or = false; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + if (ecs_term_finalize(world, f->name, f->expr, term)) { + return -1; + } + + is_or = term->oper == EcsOr; + actual_count += !(is_or && prev_or); + term->index = actual_count - 1; + prev_or = is_or; + + if (term->args[0].entity == EcsThis) { + f->match_this = true; + if (term->args[0].set.mask != EcsSelf) { + f->match_only_this = false; + } + } else { + f->match_only_this = false; + } + } + + f->term_count_actual = actual_count; + + return 0; +} + +int ecs_filter_init( + const ecs_world_t *stage, + ecs_filter_t *filter_out, + const ecs_filter_desc_t *desc) +{ + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(filter_out != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + int i, term_count = 0; + ecs_term_t *terms = desc->terms_buffer; + const char *name = desc->name; + const char *expr = desc->expr; + + ecs_filter_t f = { + /* Temporarily set the fields to the values provided in desc, until the + * filter has been validated. */ + .name = (char*)name, + .expr = (char*)expr + }; + + if (terms) { + terms = desc->terms_buffer; + term_count = desc->terms_buffer_count; + } else { + terms = (ecs_term_t*)desc->terms; + for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { + if (!ecs_term_is_initialized(&terms[i])) { + break; + } + + term_count ++; + } + } + + /* Temporarily set array from desc to filter, until the filter has been + * validated. */ + f.terms = terms; + f.term_count = term_count; + + if (expr) { +#ifdef FLECS_PARSER + int32_t buffer_count = 0; + + /* If terms have already been set, copy buffer to allocated one */ + if (terms && term_count) { + terms = ecs_os_memdup(terms, term_count * ECS_SIZEOF(ecs_term_t)); + buffer_count = term_count; + } else { + terms = NULL; + } + + /* Parse expression into array of terms */ + const char *ptr = desc->expr; + ecs_term_t term = {0}; + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ + if (!ecs_term_is_initialized(&term)) { + break; + } + + if (term_count == buffer_count) { + buffer_count = buffer_count ? buffer_count * 2 : 8; + terms = ecs_os_realloc(terms, + buffer_count * ECS_SIZEOF(ecs_term_t)); + } + + terms[term_count] = term; + term_count ++; + } + + f.terms = terms; + f.term_count = term_count; + + if (!ptr) { + goto error; + } +#else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); +#endif + } + + /* If default substitution is enabled, replace DefaultSet with SuperSet */ + if (desc->substitute_default) { + for (i = 0; i < term_count; i ++) { + if (terms[i].args[0].set.mask == EcsDefaultSet) { + terms[i].args[0].set.mask = EcsSuperSet | EcsSelf; + terms[i].args[0].set.relation = EcsIsA; + } + } + } + + /* Ensure all fields are consistent and properly filled out */ + if (ecs_filter_finalize(world, &f)) { + goto error; + } + + *filter_out = f; + + /* Copy term resources. */ + if (term_count) { + if (!filter_out->expr) { + if (term_count < ECS_TERM_CACHE_SIZE) { + filter_out->terms = filter_out->term_cache; + filter_out->term_cache_used = true; + } else { + filter_out->terms = ecs_os_malloc_n(ecs_term_t, term_count); + } + } + + for (i = 0; i < term_count; i ++) { + filter_out->terms[i] = ecs_term_move(&terms[i]); + } + } else { + filter_out->terms = NULL; + } + + filter_out->name = ecs_os_strdup(desc->name); + filter_out->expr = ecs_os_strdup(desc->expr); + + ecs_assert(!filter_out->term_cache_used || + filter_out->terms == filter_out->term_cache, + ECS_INTERNAL_ERROR, NULL); + + return 0; +error: + /* NULL members that point to non-owned resources */ + if (!f.expr) { + f.terms = NULL; + } + + f.name = NULL; + f.expr = NULL; + + ecs_filter_fini(&f); + + return -1; +} + +void ecs_filter_copy( + ecs_filter_t *dst, + const ecs_filter_t *src) +{ + if (src) { + *dst = *src; + + int32_t term_count = src->term_count; + + if (src->term_cache_used) { + dst->terms = dst->term_cache; + } else { + dst->terms = ecs_os_memdup_n(src->terms, ecs_term_t, term_count); + } + + int i; + for (i = 0; i < term_count; i ++) { + dst->terms[i] = ecs_term_copy(&src->terms[i]); + } + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} + +void ecs_filter_move( + ecs_filter_t *dst, + ecs_filter_t *src) +{ + if (src) { + *dst = *src; + + if (src->term_cache_used) { + dst->terms = dst->term_cache; + } + + src->terms = NULL; + src->term_count = 0; + } else { + ecs_os_memset_t(dst, 0, ecs_filter_t); + } +} + +void ecs_filter_fini( + ecs_filter_t *filter) +{ + if (filter->terms) { + int i, count = filter->term_count; + for (i = 0; i < count; i ++) { + ecs_term_fini(&filter->terms[i]); + } + + if (!filter->term_cache_used) { + ecs_os_free(filter->terms); + } + } + + ecs_os_free(filter->name); + ecs_os_free(filter->expr); +} + +static +void filter_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_term_id_t *id) +{ + if (id->name) { + ecs_strbuf_appendstr(buf, id->name); + } else if (id->entity) { + char *path = ecs_get_fullpath(world, id->entity); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + } else { + ecs_strbuf_appendstr(buf, "0"); + } +} + +char* ecs_filter_str( + const ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, + ECS_INTERNAL_ERROR, NULL); + + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + int32_t or_count = 0; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + + if (i) { + if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { + ecs_strbuf_appendstr(&buf, " || "); + } else { + ecs_strbuf_appendstr(&buf, ", "); + } + } + + if (or_count < 1) { + if (term->inout == EcsIn) { + ecs_strbuf_appendstr(&buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendstr(&buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendstr(&buf, "[out] "); + } + } + + if (term->role && term->role != ECS_PAIR) { + ecs_strbuf_appendstr(&buf, ecs_role_str(term->role)); + ecs_strbuf_appendstr(&buf, " "); + } + + if (term->oper == EcsOr) { + or_count ++; + } else { + or_count = 0; + } + + if (term->oper == EcsNot) { + ecs_strbuf_appendstr(&buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendstr(&buf, "?"); + } + + if (term->args[0].entity == EcsThis && + ecs_term_id_is_set(&term->args[1])) + { + ecs_strbuf_appendstr(&buf, "("); + } + + if (!ecs_term_id_is_set(&term->args[1]) && + (term->pred.entity != term->args[0].entity)) + { + filter_str_add_id(world, &buf, &term->pred); + + if (!ecs_term_id_is_set(&term->args[0])) { + ecs_strbuf_appendstr(&buf, "()"); + } else if (term->args[0].entity != EcsThis) { + ecs_strbuf_appendstr(&buf, "("); + filter_str_add_id(world, &buf, &term->args[0]); + } + + if (ecs_term_id_is_set(&term->args[1])) { + ecs_strbuf_appendstr(&buf, ", "); + filter_str_add_id(world, &buf, &term->args[1]); + ecs_strbuf_appendstr(&buf, ")"); + } + } else if (!ecs_term_id_is_set(&term->args[1])) { + ecs_strbuf_appendstr(&buf, "$"); + filter_str_add_id(world, &buf, &term->pred); + } else if (ecs_term_id_is_set(&term->args[1])) { + filter_str_add_id(world, &buf, &term->pred); + ecs_strbuf_appendstr(&buf, ", "); + filter_str_add_id(world, &buf, &term->args[1]); + ecs_strbuf_appendstr(&buf, ")"); + } + } + + return ecs_strbuf_get(&buf); +} + +static +bool populate_from_column( + ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t column, + ecs_entity_t source, + ecs_id_t *id_out, + ecs_type_t *type_out, + ecs_entity_t *subject_out, + ecs_size_t *size_out, + void **ptr_out) +{ + bool has_data = false; + + if (column != -1) { + /* If source is not This, find table of source */ + if (source) { + table = ecs_get_table(world, source); + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + column = tr->column; + } + + ecs_data_t *data = flecs_table_get_data(table); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + + /* If there is no data, ensure that iterator won't try to get it */ + if (table->column_count > column) { + ecs_column_t *c = &data->columns[column]; + if (c->size) { + has_data = true; + *size_out = c->size; + } + } + + if (!has_data) { + *size_out = 0; + } + + id = ids[column]; + + if (subject_out) { + *subject_out = source; + } + + if (ptr_out) { + if (has_data) { + if (source) { + *ptr_out = (void*)ecs_get_id(world, source, id); + } else { + ecs_column_t *col = &data->columns[column]; + *ptr_out = ecs_vector_first_t( + col->data, col->size, col->alignment); + } + } else { + *ptr_out = NULL; + } + } + } + + *type_out = NULL; + *id_out = id; + + return has_data; +} + +static +void populate_from_table( + ecs_iter_t *it, + ecs_table_t *table) +{ + it->table = table; + it->count = ecs_table_count(table); + + const ecs_data_t *data = flecs_table_get_data(table); + it->data = (ecs_data_t*)data; + + if (data) { + it->table_columns = data->columns; + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + } else { + it->table_columns = NULL; + it->entities = NULL; + } +} + +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_type_t type, + ecs_id_t *ids, + int32_t *columns, + ecs_type_t *types, + ecs_entity_t *subjects, + ecs_size_t *sizes, + void **ptrs) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(filter != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, + ECS_INTERNAL_ERROR, NULL); + + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + bool is_or = false; + bool or_result = false; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t oper = term->oper; + const ecs_table_t *match_table = table; + ecs_type_t match_type = type; + + if (!is_or && oper == EcsOr) { + is_or = true; + or_result = false; + } else if (is_or && oper != EcsOr) { + if (!or_result) { + return false; + } + + is_or = false; + } + + ecs_entity_t subj_entity = subj->entity; + if (!subj_entity) { + continue; + } + + if (subj_entity != EcsThis) { + match_table = ecs_get_table(world, subj_entity); + if (match_table) { + match_type = match_table->type; + } else { + match_type = NULL; + } + } + + ecs_entity_t source; + + int32_t column = ecs_type_match(world, match_table, match_type, + 0, term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source); + bool result = column != -1; + + if (oper == EcsNot) { + result = !result; + } + + if (oper == EcsOptional) { + result = true; + } + + if (is_or) { + or_result |= result; + } else if (!result) { + return false; + } + + if (subj_entity != EcsThis) { + if (!source) { + source = subj_entity; + } + } + + if (columns && result) { + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(types != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(subjects != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t t_i = term->index; + + void **ptr = ptrs ? &ptrs[t_i] : NULL; + populate_from_column(world, table, term->id, column, + source, &ids[t_i], &types[t_i], &subjects[t_i], &sizes[t_i], + ptr); + + if (column != -1) { + columns[t_i] = column + 1; + } else { + columns[t_i] = 0; + } + } + } + + return !is_or || or_result; +} + +static +void term_iter_init_no_data( + ecs_term_iter_t *iter) +{ + iter->term = NULL; + iter->self_index = NULL; + iter->iter = ecs_map_iter(NULL); +} + +static +void term_iter_init_wildcard( + const ecs_world_t *world, + ecs_term_iter_t *iter) +{ + iter->term = NULL; + iter->self_index = flecs_get_id_record(world, EcsWildcard); + + if (iter->self_index) { + iter->iter = ecs_map_iter(iter->self_index->table_index); + } +} + +static +void term_iter_init( + const ecs_world_t *world, + ecs_term_t *term, + ecs_term_iter_t *iter) +{ + const ecs_term_id_t *subj = &term->args[0]; + + iter->term = term; + + if (subj->set.mask == EcsDefaultSet || subj->set.mask & EcsSelf) { + iter->self_index = flecs_get_id_record(world, term->id); + } + + if (subj->set.mask & EcsSuperSet) { + iter->set_index = flecs_get_id_record(world, + ecs_pair(subj->set.relation, EcsWildcard)); + } + + if (iter->self_index) { + iter->iter = ecs_map_iter(iter->self_index->table_index); + } else if (iter->set_index) { + iter->iter = ecs_map_iter(iter->set_index->table_index); + iter->iter_set = true; + } +} + +ecs_iter_t ecs_term_iter( + const ecs_world_t *stage, + ecs_term_t *term) +{ + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term->id != 0, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + if (ecs_term_finalize(world, NULL, NULL, term)) { + /* Invalid term */ + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage, + .column_count = 1 + }; + + term_iter_init(world, term, &it.iter.term); + + return it; +} + +static +ecs_table_record_t* term_iter_next( + ecs_world_t *world, + ecs_term_iter_t *iter, + ecs_entity_t *source_out) +{ + ecs_table_t *table = NULL; + ecs_entity_t source = 0; + ecs_table_record_t *tr; + + do { + tr = ecs_map_next(&iter->iter, ecs_table_record_t, NULL); + if (!tr) { + if (!iter->iter_set) { + if (iter->set_index) { + iter->iter = ecs_map_iter(iter->set_index->table_index); + tr = ecs_map_next(&iter->iter, ecs_table_record_t, NULL); + iter->iter_set = true; + } + } + + if (!tr) { + return NULL; + } + } + + table = tr->table; + + if (!ecs_table_count(table)) { + continue; + } + + if (iter->iter_set) { + const ecs_term_t *term = iter->term; + const ecs_term_id_t *subj = &term->args[0]; + + if (iter->self_index) { + if (ecs_map_has(iter->self_index->table_index, table->id)) { + /* If the table has the id itself and this term matched Self + * we already matched it */ + continue; + } + } + + /* Test if following the relation finds the id */ + int32_t index = ecs_type_match(world, table, table->type, 0, + term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, &source); + if (index == -1) { + continue; + } + + ecs_assert(source != 0, ECS_INTERNAL_ERROR, NULL); + } + + break; + } while (true); + + if (source_out) { + *source_out = source; + } + + return tr; +} + +bool ecs_term_next( + ecs_iter_t *it) +{ + ecs_term_iter_t *iter = &it->iter.term; + ecs_term_t *term = iter->term; + ecs_world_t *world = it->real_world; + + ecs_entity_t source; + ecs_table_record_t *tr = term_iter_next(world, iter, &source); + if (!tr) { + it->is_valid = false; + return false; + } + + ecs_table_t *table = tr->table; + + /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ + ecs_assert(source || !iter->iter_set, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_data_t *data = flecs_table_get_data(table); + + it->table = table; + it->data = data; + it->ids = &iter->id; + it->types = &iter->type; + it->columns = &iter->column; + it->subjects = &iter->subject; + it->sizes = &iter->size; + it->ptrs = &iter->ptr; + + it->table_columns = data->columns; + it->count = ecs_table_count(table); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->is_valid = true; + + bool has_data = populate_from_column(world, table, term->id, tr->column, + source, &iter->id, &iter->type, &iter->subject, &iter->size, + &iter->ptr); + + if (!source) { + if (has_data) { + iter->column = tr->column + 1; + } else { + iter->column = 0; + } + } else { + iter->column = -1; /* Point to ref */ + } + + return true; +} + +ecs_iter_t ecs_filter_iter( + const ecs_world_t *stage, + const ecs_filter_t *filter) +{ + ecs_assert(stage != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + + ecs_iter_t it = { + .real_world = (ecs_world_t*)world, + .world = (ecs_world_t*)stage + }; + + ecs_filter_iter_t *iter = &it.iter.filter; + if (filter) { + iter->filter = *filter; + + if (filter->term_cache_used) { + iter->filter.terms = iter->filter.term_cache; + } + + ecs_filter_finalize(world, &iter->filter); + + ecs_assert(!filter->term_cache_used || + filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_filter_init(world, &iter->filter, &(ecs_filter_desc_t) { + .terms = {{ .id = EcsWildcard }} + }); + + filter = &iter->filter; + } + + int32_t i, term_count = filter->term_count; + ecs_term_t *terms = filter->terms; + int32_t min_count = -1; + int32_t min_term_index = -1; + + /* Find term that represents smallest superset */ + if (filter->match_this) { + iter->kind = EcsFilterIterEvalIndex; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + + ecs_assert(term != NULL, ECS_INTERNAL_ERROR, NULL); + + if (term->oper != EcsAnd) { + continue; + } + + if (term->args[0].entity != EcsThis) { + continue; + } + + ecs_id_record_t *idr = flecs_get_id_record(world, term->id); + if (!idr) { + /* If one of the terms does not match with any data, iterator + * should not return anything */ + term_iter_init_no_data(&iter->term_iter); + return it; + } + + int32_t table_count = ecs_map_count(idr->table_index); + if (min_count == -1 || table_count < min_count) { + min_count = table_count; + min_term_index = i; + } + } + + iter->min_term_index = min_term_index; + + if (min_term_index == -1) { + term_iter_init_wildcard(world, &iter->term_iter); + } else { + term_iter_init(world, &terms[min_term_index], &iter->term_iter); + } + } else { + /* If filter has no this terms, no tables need to be evaluated */ + iter->kind = EcsFilterIterEvalNone; + } + + it.column_count = filter->term_count_actual; + + if (filter->terms == filter->term_cache) { + /* Because we're returning the iterator by value, the address of the + * term cache changes. The ecs_filter_next function will set the correct + * address when it detects that terms is set to NULL */ + iter->filter.terms = NULL; + } + + return it; +} + +bool ecs_filter_next( + ecs_iter_t *it) +{ + ecs_filter_iter_t *iter = &it->iter.filter; + ecs_filter_t *filter = &iter->filter; + ecs_world_t *world = it->real_world; + + if (!filter->terms) { + filter->terms = filter->term_cache; + } + + ecs_iter_init(it); + + if (iter->kind == EcsFilterIterEvalIndex) { + ecs_term_iter_t *term_iter = &iter->term_iter; + ecs_table_t *table; + bool match; + + do { + ecs_entity_t source; + ecs_table_record_t *tr = term_iter_next(world, term_iter, &source); + if (!tr) { + goto done; + } + + table = tr->table; + match = flecs_filter_match_table(world, filter, table, table->type, + it->ids, it->columns, it->types, it->subjects, it->sizes, + it->ptrs); + } while (!match); + + populate_from_table(it, table); + + goto yield; + } + +done: + ecs_iter_fini(it); + return false; + +yield: + it->is_valid = true; + return true; +} diff --git a/fggl/ecs2/flecs/src/hash.c b/fggl/ecs2/flecs/src/hash.c new file mode 100644 index 0000000000000000000000000000000000000000..1de2cdb15df8a32cd1fd5778b45c9e93de9e91cf --- /dev/null +++ b/fggl/ecs2/flecs/src/hash.c @@ -0,0 +1,334 @@ +#include "private_api.h" + +#ifndef _MSC_VER +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif + +/* See explanation below. The hashing function may read beyond the memory passed + * into the hashing function, but only at word boundaries. This should be safe, + * but trips up address sanitizers and valgrind. + * This ensures clean valgrind logs in debug mode & the best perf in release */ +#ifndef NDEBUG +#define VALGRIND +#endif + +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + http://burtleburtle.net/bob/c/lookup3.c +------------------------------------------------------------------------------- +*/ + +#ifdef _MSC_VER +//FIXME +#else +#include <sys/param.h> /* attempt to define endianness */ +#endif +#ifdef linux +# include <endian.h> /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +#else +# define HASH_LITTLE_ENDIAN 0 +#endif + +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ +static +void hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + const uint8_t *k8; + (void)k8; + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + + final(a,b,c); + *pc=c; *pb=b; +} + +uint64_t flecs_hash( + const void *data, + ecs_size_t length) +{ + uint32_t h_1 = 0; + uint32_t h_2 = 0; + + hashlittle2( + data, + flecs_to_size_t(length), + &h_1, + &h_2); + + return h_1 | ((uint64_t)h_2 << 32); +} diff --git a/fggl/ecs2/flecs/src/hashmap.c b/fggl/ecs2/flecs/src/hashmap.c new file mode 100644 index 0000000000000000000000000000000000000000..79fbeecd65ecdccaadc1999a3e42c2b5f924daaf --- /dev/null +++ b/fggl/ecs2/flecs/src/hashmap.c @@ -0,0 +1,202 @@ +#include "private_api.h" + +typedef struct ecs_hm_bucket_t { + ecs_vector_t *keys; + ecs_vector_t *values; +} ecs_hm_bucket_t; + +static +int32_t find_key( + ecs_hashmap_t map, + ecs_vector_t *keys, + ecs_size_t key_size, + const void *key) +{ + int32_t i, count = ecs_vector_count(keys); + void *key_array = ecs_vector_first_t(keys, key_size, 8); + for (i = 0; i < count; i ++) { + void *key_ptr = ECS_OFFSET(key_array, key_size * i); + if (map.compare(key_ptr, key) == 0) { + return i; + } + } + return -1; +} + +ecs_hashmap_t _flecs_hashmap_new( + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare) +{ + return (ecs_hashmap_t){ + .key_size = key_size, + .value_size = value_size, + .compare = compare, + .hash = hash, + .impl = ecs_map_new(ecs_hm_bucket_t, 0) + }; +} + +void flecs_hashmap_free( + ecs_hashmap_t map) +{ + ecs_map_iter_t it = ecs_map_iter(map.impl); + ecs_hm_bucket_t *bucket; + while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { + ecs_vector_free(bucket->keys); + ecs_vector_free(bucket->values); + } + + ecs_map_free(map.impl); +} + +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(map.impl, ecs_hm_bucket_t, hash); + if (!bucket) { + return NULL; + } + + int32_t index = find_key(map, bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } + + return ecs_vector_get_t(bucket->values, value_size, 8, index); +} + +flecs_hashmap_result_t _flecs_hashmap_ensure( + const ecs_hashmap_t map, + ecs_size_t key_size, + 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_ensure(map.impl, ecs_hm_bucket_t, hash); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + + void *value_ptr, *key_ptr; + + ecs_vector_t *keys = bucket->keys; + if (!keys) { + bucket->keys = ecs_vector_new_t(key_size, 8, 1); + bucket->values = ecs_vector_new_t(value_size, 8, 1); + key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); + ecs_os_memcpy(key_ptr, key, key_size); + value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); + ecs_os_memset(value_ptr, 0, value_size); + } else { + int32_t index = find_key(map, keys, key_size, key); + if (index == -1) { + key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); + ecs_os_memcpy(key_ptr, key, key_size); + value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(value_ptr, 0, value_size); + } else { + key_ptr = ecs_vector_get_t(bucket->keys, key_size, 8, index); + value_ptr = ecs_vector_get_t(bucket->values, value_size, 8, index); + } + } + + return (flecs_hashmap_result_t){ + .key = key_ptr, + .value = value_ptr, + .hash = hash + }; +} + +void _flecs_hashmap_set( + const 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); +} + +void _flecs_hashmap_remove_w_hash( + const ecs_hashmap_t map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash) +{ + ecs_hm_bucket_t *bucket = ecs_map_get(map.impl, ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } + + int32_t index = find_key(map, bucket->keys, key_size, key); + if (index == -1) { + return; + } + + ecs_vector_remove_t(bucket->keys, key_size, 8, index); + ecs_vector_remove_t(bucket->values, value_size, 8, index); + + if (!ecs_vector_count(bucket->keys)) { + ecs_vector_free(bucket->keys); + ecs_vector_free(bucket->values); + ecs_map_remove(map.impl, hash); + } +} + +void _flecs_hashmap_remove( + 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); + _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_vector_count(bucket->keys)) { + bucket = it->bucket = ecs_map_next(&it->it, ecs_hm_bucket_t, NULL); + if (!bucket) { + return NULL; + } + index = it->index = 0; + } + + if (key_out) { + *(void**)key_out = ecs_vector_get_t(bucket->keys, key_size, 8, index); + } + + return ecs_vector_get_t(bucket->values, value_size, 8, index); +} diff --git a/fggl/ecs2/flecs/src/hierarchy.c b/fggl/ecs2/flecs/src/hierarchy.c new file mode 100644 index 0000000000000000000000000000000000000000..f78a18ad01984612f2d50a58a6e3be6246c6f28c --- /dev/null +++ b/fggl/ecs2/flecs/src/hierarchy.c @@ -0,0 +1,798 @@ + +#include "private_api.h" + +#define ECS_NAME_BUFFER_LENGTH (64) + +static +bool path_append( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t cur = 0; + char buff[22]; + const char *name; + + if (ecs_is_valid(world, child)) { + cur = ecs_get_object(world, child, EcsChildOf, 0); + if (cur) { + if (cur != parent && cur != EcsFlecsCore) { + path_append(world, parent, cur, sep, prefix, buf); + ecs_strbuf_appendstr(buf, sep); + } + } else if (prefix) { + ecs_strbuf_appendstr(buf, prefix); + } + + name = ecs_get_name(world, child); + if (!name) { + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; + } + } else { + ecs_os_sprintf(buff, "%u", (uint32_t)child); + name = buff; + } + + ecs_strbuf_appendstr(buf, name); + + return cur != 0; +} + +static +ecs_string_t get_string_key( + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_assert(!length || length == ecs_os_strlen(name), + ECS_INTERNAL_ERROR, NULL); + + if (!length) { + length = ecs_os_strlen(name); + } + + ecs_assert(!hash || hash == flecs_hash(name, length), + ECS_INTERNAL_ERROR, NULL); + + if (!hash) { + hash = flecs_hash(name, length); + } + + return (ecs_string_t) { + .value = (char*)name, + .length = length, + .hash = hash + }; +} + +static +ecs_entity_t find_by_name( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_string_t key = get_string_key(name, length, hash); + + ecs_entity_t *e = flecs_hashmap_get(*map, &key, ecs_entity_t); + + if (!e) { + return 0; + } + + return *e; +} + +static +void register_by_name( + ecs_hashmap_t *map, + ecs_entity_t entity, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_string_t key = get_string_key(name, length, hash); + + ecs_entity_t existing = find_by_name(map, name, key.length, key.hash); + if (existing) { + if (existing != entity) { + ecs_abort(ECS_ALREADY_DEFINED, + "conflicting entity registered with name '%s'", name); + } + } else { + key.value = ecs_os_strdup(key.value); + } + + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + *map, &key, ecs_entity_t); + + *((ecs_entity_t*)hmr.value) = entity; +} + +static +bool is_number( + const char *name) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!isdigit(name[0])) { + return false; + } + + ecs_size_t i, length = ecs_os_strlen(name); + for (i = 1; i < length; i ++) { + char ch = name[i]; + + if (!isdigit(ch)) { + break; + } + } + + return i >= length; +} + +static +ecs_entity_t name_to_id( + const char *name) +{ + long int result = atol(name); + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + return (ecs_entity_t)result; +} + +static +ecs_entity_t get_builtin( + const char *name) +{ + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; + } + + return 0; +} + +static +ecs_entity_t find_child_in_table( + const ecs_table_t *table, + const char *name) +{ + /* If table doesn't have names, then don't bother */ + int32_t name_index = ecs_type_index_of(table->type, 0, + ecs_pair(ecs_id(EcsIdentifier), EcsName)); + if (name_index == -1) { + return 0; + } + + ecs_data_t *data = flecs_table_get_data(table); + if (!data || !data->columns) { + return 0; + } + + int32_t i, count = ecs_vector_count(data->entities); + if (!count) { + return 0; + } + + ecs_column_t *column = &data->columns[name_index]; + EcsIdentifier *names = ecs_vector_first(column->data, EcsIdentifier); + + if (is_number(name)) { + return name_to_id(name); + } + + for (i = 0; i < count; i ++) { + const char *cur_name = names[i].value; + if (cur_name && !strcmp(cur_name, name)) { + return *ecs_vector_get(data->entities, ecs_entity_t, i); + } + } + + return 0; +} + +static +bool 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* path_elem( + const char *path, + const char *sep, + int32_t *len) +{ + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t count = 0; + + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + } + + ecs_assert(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + + if (!template_nesting && is_sep(&ptr, sep)) { + break; + } + + count ++; + } + + if (len) { + *len = count; + } + + if (count) { + return ptr; + } else { + return NULL; + } +} + +static +ecs_entity_t get_parent_from_path( + const ecs_world_t *world, + ecs_entity_t parent, + const char **path_ptr, + const char *prefix, + bool new_entity) +{ + bool start_from_root = false; + const char *path = *path_ptr; + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + if (prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(path, prefix, len)) { + path += len; + parent = 0; + start_from_root = true; + } + } + + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); + } + + *path_ptr = path; + + return parent; +} + +static +void on_set_symbol(ecs_iter_t *it) { + EcsIdentifier *n = ecs_term(it, EcsIdentifier, 1); + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + register_by_name( + &world->symbols, e, n[i].value, n[i].length, n[i].hash); + } +} + +static +uint64_t string_hash( + const void *ptr) +{ + const ecs_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INVALID_PARAMETER, NULL); + return str->hash; +} + +static +int string_compare( + const void *ptr1, + const void *ptr2) +{ + const ecs_string_t *str1 = ptr1; + const ecs_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); +} + +ecs_hashmap_t flecs_string_hashmap_new(void) { + return flecs_hashmap_new(ecs_string_t, ecs_entity_t, + string_hash, + string_compare); +} + +void flecs_bootstrap_hierarchy(ecs_world_t *world) { + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol)}, + .callback = on_set_symbol, + .events = {EcsOnSet} + }); +} + + +/* Public functions */ + +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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + if (!sep) { + sep = "."; + } + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (parent != child) { + path_append(world, parent, child, sep, prefix, &buf); + } else { + ecs_strbuf_appendstr(&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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + ecs_entity_t result = 0; + + ecs_id_record_t *r = flecs_get_id_record(world, ecs_pair(EcsChildOf, parent)); + if (r && r->table_index) { + ecs_map_iter_t it = ecs_map_iter(r->table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + result = find_child_in_table(tr->table, name); + if (result) { + return result; + } + } + } + + return result; +} + +ecs_entity_t ecs_lookup( + const ecs_world_t *world, + const char *name) +{ + if (!name) { + return 0; + } + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = get_builtin(name); + if (e) { + return e; + } + + if (is_number(name)) { + return name_to_id(name); + } + + e = find_by_name(&world->aliases, name, 0, 0); + if (e) { + return e; + } + + return ecs_lookup_child(world, 0, name); +} + +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *name, + bool lookup_as_path) +{ + if (!name) { + return 0; + } + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = find_by_name(&world->symbols, name, 0, 0); + if (e) { + return e; + } + + if (lookup_as_path) { + return ecs_lookup_fullpath(world, name); + } + + 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; + } + + if (!sep) { + sep = "."; + } + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + ecs_entity_t e = get_builtin(path); + if (e) { + return e; + } + + e = find_by_name(&world->aliases, path, 0, 0); + if (e) { + return e; + } + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr, *ptr_start; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + ecs_entity_t cur; + bool core_searched = false; + + if (!sep) { + sep = "."; + } + + parent = get_parent_from_path(world, parent, &path, prefix, true); + +retry: + cur = parent; + ptr_start = ptr = path; + + while ((ptr = path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; + } + } + +tail: + if (!cur && recursive) { + if (!core_searched) { + if (parent) { + parent = ecs_get_object(world, parent, EcsChildOf, 0); + } else { + parent = EcsFlecsCore; + core_searched = true; + } + goto retry; + } + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +} + +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_entity_t e = ecs_pair(EcsChildOf, scope); + ecs_ids_t to_add = { + .array = &e, + .count = 1 + }; + + ecs_entity_t cur = stage->scope; + stage->scope = scope; + + if (scope) { + stage->scope_table = flecs_table_traverse_add( + world, &world->store.root, &to_add, NULL); + } else { + stage->scope_table = &world->store.root; + } + + return cur; +} + +ecs_entity_t ecs_get_scope( + const ecs_world_t *world) +{ + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->scope; +} + +int32_t ecs_get_child_count( + const ecs_world_t *world, + ecs_entity_t parent) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + int32_t count = 0; + + ecs_id_record_t *r = flecs_get_id_record(world, ecs_pair(EcsChildOf, parent)); + if (r && r->table_index) { + ecs_map_iter_t it = ecs_map_iter(r->table_index); + ecs_table_record_t *tr; + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + count += ecs_table_count(tr->table); + } + } + + return count; +} + +ecs_iter_t ecs_scope_iter_w_filter( + ecs_world_t *iter_world, + ecs_entity_t parent, + ecs_filter_t *filter) +{ + ecs_assert(iter_world != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_world_t *world = (ecs_world_t*)ecs_get_world(iter_world); + ecs_iter_t it = { + .world = iter_world + }; + + ecs_id_record_t *r = flecs_get_id_record( + world, ecs_pair(EcsChildOf, parent)); + if (r && r->table_index) { + it.iter.parent.tables = ecs_map_iter(r->table_index); + it.table_count = ecs_map_count(r->table_index); + if (filter) { + it.iter.parent.filter = *filter; + } + } + + return it; +} + +ecs_iter_t ecs_scope_iter( + ecs_world_t *iter_world, + ecs_entity_t parent) +{ + return ecs_scope_iter_w_filter(iter_world, parent, NULL); +} + +bool ecs_scope_next( + ecs_iter_t *it) +{ + ecs_scope_iter_t *iter = &it->iter.parent; + ecs_map_iter_t *tables = &iter->tables; + ecs_filter_t filter = iter->filter; + ecs_table_record_t *tr; + + while ((tr = ecs_map_next(tables, ecs_table_record_t, NULL))) { + ecs_table_t *table = tr->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + iter->index ++; + + ecs_data_t *data = flecs_table_get_data(table); + if (!data) { + continue; + } + + it->count = ecs_table_count(table); + if (!it->count) { + continue; + } + + if (filter.include || filter.exclude) { + if (!flecs_table_match_filter(it->world, table, &filter)) { + continue; + } + } + + it->table = table; + it->table_columns = data->columns; + it->count = ecs_table_count(table); + it->entities = ecs_vector_first(data->entities, ecs_entity_t); + it->is_valid = true; + + goto yield; + } + + it->is_valid = false; + return false; + +yield: + it->is_valid = true; + return true; +} + +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + const char *old_prefix = world->name_prefix; + world->name_prefix = prefix; + return old_prefix; +} + +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_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!sep) { + sep = "."; + } + + if (!path) { + if (!entity) { + entity = ecs_new_id(world); + } + + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); + } + + return entity; + } + + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr = path; + const char *ptr_start = path; + char *elem = buff; + int32_t len, size = ECS_NAME_BUFFER_LENGTH; + + parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); + + ecs_entity_t cur = parent; + + char *name = NULL; + + while ((ptr = path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; + } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; + } + + elem[len] = '\0'; + ptr_start = ptr; + + 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 */ + if (entity && !path_elem(ptr, sep, NULL)) { + e = entity; + } + + if (!e) { + e = ecs_new_id(world); + } + + ecs_set_name(world, e, name); + + if (cur) { + ecs_add_pair(world, e, EcsChildOf, cur); + } + } + + cur = e; + } + + if (entity && (cur != entity)) { + if (name) { + ecs_os_free(name); + } + + name = ecs_os_strdup(elem); + + ecs_set_name(world, entity, name); + } + + if (name) { + ecs_os_free(name); + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +} + +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) +{ + if (!sep) { + sep = "."; + } + + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); +} + +void ecs_use( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + register_by_name(&world->aliases, entity, name, 0, 0); +} diff --git a/fggl/ecs2/flecs/src/iter.c b/fggl/ecs2/flecs/src/iter.c new file mode 100644 index 0000000000000000000000000000000000000000..1d75ce6842cb528d73972973b50b147dce8ab03a --- /dev/null +++ b/fggl/ecs2/flecs/src/iter.c @@ -0,0 +1,243 @@ +#include "private_api.h" + +#define INIT_CACHE(it, f, term_count)\ + if (!it->f && term_count) {\ + if (term_count < ECS_TERM_CACHE_SIZE) {\ + it->f = it->cache.f;\ + it->cache.f##_alloc = false;\ + } else {\ + it->f = ecs_os_malloc(ECS_SIZEOF(*(it->f)) * term_count);\ + it->cache.f##_alloc = true;\ + }\ + } + +#define FINI_CACHE(it, f)\ + if (it->f) {\ + if (it->cache.f##_alloc) {\ + ecs_os_free((void*)it->f);\ + }\ + } + +void ecs_iter_init( + ecs_iter_t *it) +{ + INIT_CACHE(it, ids, it->column_count); + INIT_CACHE(it, types, it->column_count); + INIT_CACHE(it, columns, it->column_count); + INIT_CACHE(it, subjects, it->column_count); + INIT_CACHE(it, sizes, it->column_count); + INIT_CACHE(it, ptrs, it->column_count); + + it->is_valid = true; +} + +void ecs_iter_fini( + ecs_iter_t *it) +{ + ecs_assert(it->is_valid == true, ECS_INVALID_PARAMETER, NULL); + it->is_valid = false; + + FINI_CACHE(it, ids); + FINI_CACHE(it, types); + FINI_CACHE(it, columns); + FINI_CACHE(it, subjects); + FINI_CACHE(it, sizes); + FINI_CACHE(it, ptrs); +} + +/* --- Public API --- */ + +void* ecs_term_w_size( + const ecs_iter_t *it, + size_t size, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || ecs_term_size(it, term) == size, + ECS_INVALID_PARAMETER, NULL); + + (void)size; + + if (!term) { + return it->entities; + } + + if (!it->ptrs) { + return NULL; + } + + return it->ptrs[term - 1]; +} + +bool ecs_term_is_owned( + const ecs_iter_t *it, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term > 0, ECS_INVALID_PARAMETER, NULL); + return it->subjects == NULL || it->subjects[term - 1] == 0; +} + +bool ecs_term_is_readonly( + const ecs_iter_t *it, + int32_t term_index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term_index > 0, ECS_INVALID_PARAMETER, NULL); + + ecs_query_t *query = it->query; + + /* If this is not a query iterator, readonly is meaningless */ + ecs_assert(query != NULL, ECS_INVALID_OPERATION, NULL); + (void)query; + + ecs_term_t *term = &it->query->filter.terms[term_index - 1]; + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + + if (term->inout == EcsIn) { + return true; + } else { + ecs_term_id_t *subj = &term->args[0]; + + if (term->inout == EcsInOutDefault) { + if (subj->entity != EcsThis) { + return true; + } + + if ((subj->set.mask != EcsSelf) && + (subj->set.mask != EcsDefaultSet)) + { + return true; + } + } + } + + return false; +} + +bool ecs_term_is_set( + const ecs_iter_t *it, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(term > 0, ECS_INVALID_PARAMETER, NULL); + + return it->columns[term - 1] != 0; +} + +ecs_entity_t ecs_term_source( + const ecs_iter_t *it, + int32_t term) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(term > 0, ECS_INVALID_PARAMETER, NULL); + + if (!it->subjects) { + return 0; + } else { + return it->subjects[term - 1]; + } +} + +ecs_id_t ecs_term_id( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index > 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->ids != NULL, ECS_INTERNAL_ERROR, NULL); + return it->ids[index - 1]; +} + +size_t ecs_term_size( + const ecs_iter_t *it, + int32_t index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index <= it->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + + if (index == 0) { + return sizeof(ecs_entity_t); + } + + return flecs_to_size_t(it->sizes[index - 1]); +} + +ecs_table_t* ecs_iter_table( + const ecs_iter_t *it) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + return it->table; +} + +ecs_type_t ecs_iter_type( + const ecs_iter_t *it) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + + /* If no table is set it means that the iterator isn't pointing to anything + * yet. The most likely cause for this is that the operation is invoked on + * a new iterator for which "next" hasn't been invoked yet, or on an + * iterator that is out of elements. */ + ecs_table_t *table = ecs_iter_table(it); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + return table->type; +} + +int32_t ecs_iter_find_column( + const ecs_iter_t *it, + ecs_entity_t component) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + return ecs_type_index_of(it->table->type, 0, component); +} + +void* ecs_iter_column_w_size( + const ecs_iter_t *it, + size_t size, + int32_t column_index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + (void)size; + + ecs_table_t *table = it->table; + ecs_assert(column_index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + + if (table->column_count <= column_index) { + return NULL; + } + + ecs_column_t *columns = it->table_columns; + ecs_column_t *column = &columns[column_index]; + ecs_assert(!size || (ecs_size_t)size == column->size, ECS_INVALID_PARAMETER, NULL); + + return ecs_vector_first_t(column->data, column->size, column->alignment); +} + +size_t ecs_iter_column_size( + const ecs_iter_t *it, + int32_t column_index) +{ + ecs_assert(it->is_valid, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->table != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_t *table = it->table; + ecs_assert(column_index < ecs_vector_count(table->type), + ECS_INVALID_PARAMETER, NULL); + + if (table->column_count <= column_index) { + return 0; + } + + ecs_column_t *columns = it->table_columns; + ecs_column_t *column = &columns[column_index]; + + return flecs_to_size_t(column->size); +} diff --git a/fggl/ecs2/flecs/src/log.c b/fggl/ecs2/flecs/src/log.c new file mode 100644 index 0000000000000000000000000000000000000000..dbed79f9c95d2a1d3e89a0522bb88ea163ab87b8 --- /dev/null +++ b/fggl/ecs2/flecs/src/log.c @@ -0,0 +1,446 @@ +#include "private_api.h" + +static +char *ecs_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_vsprintf(result, fmt, args); + + return result; +} + +static +char* ecs_colorize( + char *msg, + bool enable_colors) +{ + ecs_strbuf_t buff = ECS_STRBUF_INIT; + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; + + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; + + if (!overrideColor) { + if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + isNum = false; + } + if (isStr && (isStr == ch) && prev != '\\') { + isStr = '\0'; + } else if (((ch == '\'') || (ch == '"')) && !isStr && + !isalpha(prev) && (prev != '\\')) + { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + isStr = ch; + } + + if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || + (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && + !isalpha(prev) && !isdigit(prev) && (prev != '_') && + (prev != '.')) + { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_GREEN); + isNum = true; + } + + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + isVar = false; + } + + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendstr(&buff, 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_appendstr(&buff, ECS_GREEN); + } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_RED); + } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_BLUE); + } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_MAGENTA); + } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_CYAN); + } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_YELLOW); + } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_GREY); + } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_BOLD); + } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { + overrideColor = false; + if (enable_colors) ecs_strbuf_appendstr(&buff, 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_appendstr(&buff, ECS_NORMAL); + overrideColor = false; + isNum = false; + isStr = false; + isVar = false; + } + } + + if (!dontAppend) { + ecs_strbuf_appendstrn(&buff, ptr, 1); + } + + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } + } + + prev = ch; + } + + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendstr(&buff, ECS_NORMAL); + } + + return ecs_strbuf_get(&buff); +} + +static int trace_indent = 0; +static int trace_level = 0; +static bool trace_color = true; + +static +void ecs_log_print( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) +{ + (void)level; + (void)line; + + if (level > trace_level) { + return; + } + + /* Massage filename so it doesn't take up too much space */ + char file_buf[256]; + ecs_os_strcpy(file_buf, file); + file = file_buf; + + char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); + } + + if (file_ptr) { + file = file_ptr + 1; + } else { + file = file_buf; + } + + char indent[32]; + int i; + for (i = 0; i < trace_indent; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } + indent[i * 2] = '\0'; + + char *msg_nocolor = ecs_vasprintf(fmt, args); + char *msg = ecs_colorize(msg_nocolor, trace_color); + + if (trace_color) { + if (level >= 0) { + ecs_os_log("%sinfo%s: %s%s%s%s:%s%d%s: %s", ECS_MAGENTA, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } else if (level == -2) { + ecs_os_warn("%swarn%s: %s%s%s%s:%s%d%s: %s", ECS_YELLOW, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } else if (level == -3) { + ecs_os_err("%serr%s: %s%s%s%s:%s%d%s: %s", ECS_RED, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } else if (level == -4) { + ecs_os_err("%sfatal%s: %s%s%s%s:%s%d%s: %s", ECS_RED, ECS_NORMAL, + ECS_GREY, indent, ECS_NORMAL, file, ECS_GREEN, line, ECS_NORMAL, + msg); + } + } else { + if (level >= 0) { + ecs_os_log("info: %s%s:%d: %s", indent, file, line, msg); + } else if (level == -2) { + ecs_os_warn("warn: %s%s:%d: %s", indent, file, line, msg); + } else if (level == -3) { + ecs_os_err("err: %s%s:%d: %s", indent, file, line, msg); + } else if (level == -4) { + ecs_os_err("fatal: %s%s:%d: %s", indent, file, line, msg); + } + } + + ecs_os_free(msg); + ecs_os_free(msg_nocolor); +} + +void _ecs_trace( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(level, file, line, fmt, args); + va_end(args); +} + +void _ecs_warn( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(-2, file, line, fmt, args); + va_end(args); +} + +void _ecs_err( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(-3, file, line, fmt, args); + va_end(args); +} + +void _ecs_fatal( + const char *file, + int32_t line, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + ecs_log_print(-4, file, line, fmt, args); + va_end(args); +} + +void ecs_log_push(void) { + trace_indent ++; +} + +void ecs_log_pop(void) { + trace_indent --; +} + +void ecs_tracing_enable( + int level) +{ + trace_level = level; +} + +void ecs_tracing_color_enable( + bool enabled) +{ + trace_color = enabled; +} + +void _ecs_parser_error( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + if (trace_level >= -2) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + + if (column != -1) { + if (name) { + ecs_os_err("%s:%d: error: %s", name, column + 1, msg); + } else { + ecs_os_err("%d: error: %s", column + 1, msg); + } + } else { + if (name) { + ecs_os_err("%s: error: %s", name, msg); + } else { + ecs_os_err("error: %s", msg); + } + } + + ecs_os_err(" %s", expr); + + if (column != -1) { + ecs_os_err(" %*s^", column, ""); + } else { + ecs_os_err(""); + } + + ecs_os_free(msg); + } + + ecs_os_abort(); +} + +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 = ecs_vasprintf(fmt, args); + va_end(args); + _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + _ecs_fatal(file, line, "%s", ecs_strerror(err)); + } + + ecs_os_abort(); +} + +void _ecs_assert( + bool condition, + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (!condition) { + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = ecs_vasprintf(fmt, args); + va_end(args); + _ecs_fatal(file, line, "assert(%s) %s (%s)", + cond_str, msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + _ecs_fatal(file, line, "assert(%s) %s", + cond_str, ecs_strerror(err)); + } + + ecs_os_abort(); + } +} + +void _ecs_deprecated( + const char *file, + int32_t line, + const char *msg) +{ + _ecs_err(file, line, "%s", msg); +} + +#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_TYPE_NOT_AN_ENTITY); + 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_OUT_OF_MEMORY); + ECS_ERR_STR(ECS_MODULE_UNDEFINED); + 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_HAS_NO_DATA); + ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); + ECS_ERR_STR(ECS_INVALID_WHILE_ITERATING); + ECS_ERR_STR(ECS_INVALID_FROM_WORKER); + ECS_ERR_STR(ECS_OUT_OF_RANGE); + ECS_ERR_STR(ECS_THREAD_ERROR); + ECS_ERR_STR(ECS_MISSING_OS_API); + ECS_ERR_STR(ECS_UNSUPPORTED); + ECS_ERR_STR(ECS_NO_OUT_COLUMNS); + ECS_ERR_STR(ECS_COLUMN_ACCESS_VIOLATION); + ECS_ERR_STR(ECS_DESERIALIZE_FORMAT_ERROR); + ECS_ERR_STR(ECS_TYPE_CONSTRAINT_VIOLATION); + ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); + ECS_ERR_STR(ECS_TYPE_INVALID_CASE); + ECS_ERR_STR(ECS_INCONSISTENT_NAME); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); + ECS_ERR_STR(ECS_INVALID_OPERATION); + ECS_ERR_STR(ECS_INVALID_DELETE); + ECS_ERR_STR(ECS_CYCLE_DETECTED); + ECS_ERR_STR(ECS_LOCKED_STORAGE); + } + + return "unknown error code"; +} diff --git a/fggl/ecs2/flecs/src/map.c b/fggl/ecs2/flecs/src/map.c new file mode 100644 index 0000000000000000000000000000000000000000..621fd80110cbb06c8acbfd5b44f79342f4d7bcfa --- /dev/null +++ b/fggl/ecs2/flecs/src/map.c @@ -0,0 +1,524 @@ +#include "private_api.h" + +/* The ratio used to determine whether the map should rehash. If + * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define LOAD_FACTOR (1.5f) +#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) +#define GET_ELEM(array, elem_size, index) \ + ECS_OFFSET(array, (elem_size) * (index)) + +typedef struct ecs_bucket_t { + ecs_map_key_t *keys; /* Array with keys */ + void *payload; /* Payload array */ + int32_t count; /* Number of elements in bucket */ +} ecs_bucket_t; + +struct ecs_map_t { + ecs_bucket_t *buckets; + int32_t elem_size; + int32_t bucket_count; + int32_t count; +}; + +/* Get bucket count for number of elements */ +static +int32_t get_bucket_count( + int32_t element_count) +{ + return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); +} + +/* Get bucket index for provided map key */ +static +int32_t get_bucket_id( + int32_t bucket_count, + ecs_map_key_t key) +{ + ecs_assert(bucket_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t result = (int32_t)(key & ((uint64_t)bucket_count - 1)); + ecs_assert(result < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return result; +} + +/* Get bucket for key */ +static +ecs_bucket_t* get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) +{ + int32_t bucket_count = map->bucket_count; + if (!bucket_count) { + return NULL; + } + + int32_t bucket_id = get_bucket_id(bucket_count, key); + ecs_assert(bucket_id < bucket_count, ECS_INTERNAL_ERROR, NULL); + + return &map->buckets[bucket_id]; +} + +/* Ensure that map has at least new_count buckets */ +static +void ensure_buckets( + ecs_map_t *map, + int32_t new_count) +{ + int32_t bucket_count = map->bucket_count; + new_count = flecs_next_pow_of_2(new_count); + if (new_count && new_count > bucket_count) { + map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); + map->bucket_count = new_count; + + ecs_os_memset( + ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), + 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); + } +} + +/* Free contents of bucket */ +static +void clear_bucket( + ecs_bucket_t *bucket) +{ + ecs_os_free(bucket->keys); + ecs_os_free(bucket->payload); + bucket->keys = NULL; + bucket->payload = NULL; + bucket->count = 0; +} + +/* Clear all buckets */ +static +void clear_buckets( + ecs_map_t *map) +{ + ecs_bucket_t *buckets = map->buckets; + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + clear_bucket(&buckets[i]); + } + ecs_os_free(buckets); + map->buckets = NULL; + map->bucket_count = 0; +} + +/* Find or create bucket for specified key */ +static +ecs_bucket_t* ensure_bucket( + ecs_map_t *map, + ecs_map_key_t key) +{ + if (!map->bucket_count) { + ensure_buckets(map, 2); + } + + int32_t bucket_id = get_bucket_id(map->bucket_count, key); + ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} + +/* Add element to bucket */ +static +int32_t add_to_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + int32_t index = bucket->count ++; + int32_t bucket_count = index + 1; + + bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); + bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); + bucket->keys[index] = key; + + if (payload) { + void *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_os_memcpy(elem, payload, elem_size); + } + + return index; +} + +/* Remove element from bucket */ +static +void remove_from_bucket( + ecs_bucket_t *bucket, + ecs_size_t elem_size, + ecs_map_key_t key, + int32_t index) +{ + (void)key; + + ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_count = -- bucket->count; + + if (index != bucket->count) { + ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); + bucket->keys[index] = bucket->keys[bucket_count]; + + ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); + ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); + + ecs_os_memcpy(elem, last_elem, elem_size); + } +} + +/* Get payload pointer for key from bucket */ +static +void* get_from_bucket( + ecs_bucket_t *bucket, + ecs_map_key_t key, + ecs_size_t elem_size) +{ + ecs_map_key_t *keys = bucket->keys; + int32_t i, count = bucket->count; + + for (i = 0; i < count; i ++) { + if (keys[i] == key) { + return GET_ELEM(bucket->payload, elem_size, i); + } + } + return NULL; +} + +/* Grow number of buckets */ +static +void rehash( + ecs_map_t *map, + int32_t bucket_count) +{ + ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); + + ecs_size_t elem_size = map->elem_size; + + ensure_buckets(map, bucket_count); + + ecs_bucket_t *buckets = map->buckets; + ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t bucket_id; + + /* Iterate backwards as elements could otherwise be moved to existing + * buckets which could temporarily cause the number of elements in a + * bucket to exceed BUCKET_COUNT. */ + for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { + ecs_bucket_t *bucket = &buckets[bucket_id]; + + int i, count = bucket->count; + ecs_map_key_t *key_array = bucket->keys; + void *payload_array = bucket->payload; + + for (i = 0; i < count; i ++) { + ecs_map_key_t key = key_array[i]; + void *elem = GET_ELEM(payload_array, elem_size, i); + int32_t new_bucket_id = get_bucket_id(bucket_count, key); + + if (new_bucket_id != bucket_id) { + ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; + + add_to_bucket(new_bucket, elem_size, key, elem); + remove_from_bucket(bucket, elem_size, key, i); + + count --; + i --; + } + } + + if (!bucket->count) { + clear_bucket(bucket); + } + } +} + +ecs_map_t* _ecs_map_new( + ecs_size_t elem_size, + ecs_size_t alignment, + int32_t element_count) +{ + (void)alignment; + + ecs_map_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_map_t) * 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + int32_t bucket_count = get_bucket_count(element_count); + + result->count = 0; + result->elem_size = elem_size; + + ensure_buckets(result, bucket_count); + + return result; +} + +void ecs_map_free( + ecs_map_t *map) +{ + if (map) { + clear_buckets(map); + ecs_os_free(map); + } +} + +void* _ecs_map_get( + const ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + (void)elem_size; + + if (!map) { + return NULL; + } + + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return NULL; + } + + return get_from_bucket(bucket, key, elem_size); +} + +void* _ecs_map_get_ptr( + const ecs_map_t *map, + ecs_map_key_t key) +{ + void * ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); + + if (ptr_ptr) { + return *(void**)ptr_ptr; + } else { + return NULL; + } +} + +bool ecs_map_has( + const ecs_map_t *map, + ecs_map_key_t key) +{ + if (!map) { + return false; + } + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return false; + } + + return get_from_bucket(bucket, key, 0) != NULL; +} + +void * _ecs_map_ensure( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + void *result = _ecs_map_get(map, elem_size, key); + if (!result) { + result = _ecs_map_set(map, elem_size, key, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(result, 0, elem_size); + } + + return result; +} + +void* _ecs_map_set( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key, + const void *payload) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = ensure_bucket(map, key); + ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + + void *elem = get_from_bucket(bucket, key, elem_size); + if (!elem) { + int32_t index = add_to_bucket(bucket, elem_size, key, payload); + + int32_t map_count = ++map->count; + int32_t target_bucket_count = get_bucket_count(map_count); + int32_t map_bucket_count = map->bucket_count; + + if (target_bucket_count > map_bucket_count) { + rehash(map, target_bucket_count); + bucket = ensure_bucket(map, key); + return get_from_bucket(bucket, key, elem_size); + } else { + return GET_ELEM(bucket->payload, elem_size, index); + } + } else { + if (payload) { + ecs_os_memcpy(elem, payload, elem_size); + } + return elem; + } +} + +void ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t * bucket = get_bucket(map, key); + if (!bucket) { + return; + } + + int32_t i, bucket_count = bucket->count; + for (i = 0; i < bucket_count; i ++) { + if (bucket->keys[i] == key) { + remove_from_bucket(bucket, map->elem_size, key, i); + map->count --; + } + } +} + +int32_t ecs_map_count( + const ecs_map_t *map) +{ + return map ? map->count : 0; +} + +int32_t ecs_map_bucket_count( + const ecs_map_t *map) +{ + return map ? map->bucket_count : 0; +} + +void ecs_map_clear( + ecs_map_t *map) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + clear_buckets(map); + map->count = 0; +} + +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .bucket_index = 0, + .element_index = 0 + }; +} + +void* _ecs_map_next( + ecs_map_iter_t *iter, + ecs_size_t elem_size, + ecs_map_key_t *key_out) +{ + const ecs_map_t *map = iter->map; + if (!map) { + return NULL; + } + + ecs_assert(!elem_size || elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + + ecs_bucket_t *bucket = iter->bucket; + int32_t element_index = iter->element_index; + elem_size = map->elem_size; + + do { + if (!bucket) { + int32_t bucket_index = iter->bucket_index; + ecs_bucket_t *buckets = map->buckets; + if (bucket_index < map->bucket_count) { + bucket = &buckets[bucket_index]; + iter->bucket = bucket; + + element_index = 0; + iter->element_index = 0; + } else { + return NULL; + } + } + + if (element_index < bucket->count) { + iter->element_index = element_index + 1; + break; + } else { + bucket = NULL; + iter->bucket_index ++; + } + } while (true); + + if (key_out) { + *key_out = bucket->keys[element_index]; + } + + return GET_ELEM(bucket->payload, elem_size, element_index); +} + +void* _ecs_map_next_ptr( + ecs_map_iter_t *iter, + ecs_map_key_t *key_out) +{ + void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); + if (result) { + return *(void**)result; + } else { + return NULL; + } +} + +void ecs_map_grow( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t target_count = map->count + element_count; + int32_t bucket_count = get_bucket_count(target_count); + + if (bucket_count > map->bucket_count) { + rehash(map, bucket_count); + } +} + +void ecs_map_set_size( + ecs_map_t *map, + int32_t element_count) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_count = get_bucket_count(element_count); + + if (bucket_count) { + rehash(map, bucket_count); + } +} + +void ecs_map_memory( + ecs_map_t *map, + int32_t *allocd, + int32_t *used) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + + if (used) { + *used = map->count * map->elem_size; + } + + if (allocd) { + *allocd += ECS_SIZEOF(ecs_map_t); + + int i, bucket_count = map->bucket_count; + for (i = 0; i < bucket_count; i ++) { + ecs_bucket_t *bucket = &map->buckets[i]; + *allocd += KEY_SIZE * bucket->count; + *allocd += map->elem_size * bucket->count; + } + + *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; + } +} diff --git a/fggl/ecs2/flecs/src/misc.c b/fggl/ecs2/flecs/src/misc.c new file mode 100644 index 0000000000000000000000000000000000000000..c4df9680cc85655ff4fd73b32ea506695363f4b4 --- /dev/null +++ b/fggl/ecs2/flecs/src/misc.c @@ -0,0 +1,316 @@ +#include "private_api.h" + +int8_t flflecs_to_i8( + int64_t v) +{ + ecs_assert(v < INT8_MAX, ECS_INTERNAL_ERROR, NULL); + return (int8_t)v; +} + +int16_t flecs_to_i16( + int64_t v) +{ + ecs_assert(v < INT16_MAX, ECS_INTERNAL_ERROR, NULL); + return (int16_t)v; +} + +uint32_t flecs_to_u32( + uint64_t v) +{ + ecs_assert(v < UINT32_MAX, ECS_INTERNAL_ERROR, NULL); + return (uint32_t)v; +} + +size_t flecs_to_size_t( + int64_t size) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); + return (size_t)size; +} + +ecs_size_t flecs_from_size_t( + size_t size) +{ + ecs_assert(size < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + return (ecs_size_t)size; +} + +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_entity_compare_qsort( + const void *e1, + const void *e2) +{ + ecs_entity_t v1 = *(ecs_entity_t*)e1; + ecs_entity_t v2 = *(ecs_entity_t*)e2; + return flecs_entity_compare(v1, NULL, v2, NULL); +} + +uint64_t flecs_string_hash( + const void *ptr) +{ + const ecs_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} + +/* + This code was taken from sokol_time.h + + zlib/libpng license + Copyright (c) 2018 Andre Weissflog + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "private_api.h" + +static int ecs_os_time_initialized; + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +static double _ecs_os_time_win_freq; +static LARGE_INTEGER _ecs_os_time_win_start; +#elif defined(__APPLE__) && defined(__MACH__) +#include <mach/mach_time.h> +static mach_timebase_info_data_t _ecs_os_time_osx_timebase; +static uint64_t _ecs_os_time_osx_start; +#else /* anything else, this will need more care for non-Linux platforms */ +#include <time.h> +static uint64_t _ecs_os_time_posix_start; +#endif + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +int64_t 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 + +void flecs_os_time_setup(void) { + if ( ecs_os_time_initialized) { + return; + } + + ecs_os_time_initialized = 1; + #if defined(_WIN32) + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&_ecs_os_time_win_start); + _ecs_os_time_win_freq = (double)freq.QuadPart / 1000000000.0; + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_ecs_os_time_osx_timebase); + _ecs_os_time_osx_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _ecs_os_time_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +uint64_t flecs_os_time_now(void) { + ecs_assert(ecs_os_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t now; + + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)(qpc_t.QuadPart / _ecs_os_time_win_freq); + #elif defined(__APPLE__) && defined(__MACH__) + now = (uint64_t) int64_muldiv((int64_t)mach_absolute_time(), (int64_t)_ecs_os_time_osx_timebase.numer, (int64_t)_ecs_os_time_osx_timebase.denom); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec); + #endif + + return now; +} + +void flecs_os_time_sleep( + int32_t sec, + int32_t nanosec) +{ +#ifndef _WIN32 + 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_os_err("nanosleep failed"); + } +#else + 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); +#endif +} + + +#if defined(_WIN32) + +static ULONG win32_current_resolution; + +void flecs_increase_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); + if (!hntdll) { + return; + } + + LONG (__stdcall *pNtSetTimerResolution)( + ULONG desired, BOOLEAN set, ULONG * current); + + pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) + GetProcAddress(hntdll, "NtSetTimerResolution"); + + if(!pNtSetTimerResolution) { + return; + } + + ULONG current, resolution = 10000; /* 1 ms */ + + if (!enable && win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + win32_current_resolution = 0; + return; + } else if (!enable) { + return; + } + + if (resolution == win32_current_resolution) { + return; + } + + if (win32_current_resolution) { + pNtSetTimerResolution(win32_current_resolution, 0, ¤t); + } + + if (pNtSetTimerResolution(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(pNtSetTimerResolution(resolution, 1, ¤t)) return; + } + + win32_current_resolution = resolution; +} + +#else +void flecs_increase_timer_resolution(bool enable) +{ + (void)enable; + return; +} +#endif diff --git a/fggl/ecs2/flecs/src/modules/pipeline/pipeline.c b/fggl/ecs2/flecs/src/modules/pipeline/pipeline.c new file mode 100644 index 0000000000000000000000000000000000000000..95207270111bbd6b76d682230e3da0ada1ef8525 --- /dev/null +++ b/fggl/ecs2/flecs/src/modules/pipeline/pipeline.c @@ -0,0 +1,760 @@ +#include "flecs.h" + +#ifdef FLECS_PIPELINE + +#include "pipeline.h" + +ECS_TYPE_DECL(EcsPipelineQuery); + +static ECS_CTOR(EcsPipelineQuery, ptr, { + memset(ptr, 0, _size); +}) + +static ECS_DTOR(EcsPipelineQuery, ptr, { + ecs_vector_free(ptr->ops); +}) + +static +int compare_entity( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); +} + +static +int group_by_phase( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t pipeline, + void *ctx) +{ + (void)ctx; + + const EcsType *pipeline_type = ecs_get(world, pipeline, EcsType); + ecs_assert(pipeline_type != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Find tag in system that belongs to pipeline */ + ecs_entity_t *sys_comps = ecs_vector_first(type, ecs_entity_t); + int32_t c, t, count = ecs_vector_count(type); + + ecs_entity_t *tags = ecs_vector_first(pipeline_type->normalized, ecs_entity_t); + int32_t tag_count = ecs_vector_count(pipeline_type->normalized); + + ecs_entity_t result = 0; + + for (c = 0; c < count; c ++) { + ecs_entity_t comp = sys_comps[c]; + for (t = 0; t < tag_count; t ++) { + if (comp == tags[t]) { + result = comp; + break; + } + } + if (result) { + break; + } + } + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL); + + return (int)result; +} + +typedef enum ComponentWriteState { + NotWritten = 0, + WriteToMain, + WriteToStage +} ComponentWriteState; + +typedef struct write_state_t { + ecs_map_t *components; + bool wildcard; +} write_state_t; + +static +int32_t get_write_state( + ecs_map_t *write_state, + ecs_entity_t component) +{ + int32_t *ptr = ecs_map_get(write_state, int32_t, component); + if (ptr) { + return *ptr; + } else { + return 0; + } +} + +static +void set_write_state( + write_state_t *write_state, + ecs_entity_t component, + int32_t value) +{ + if (component == EcsWildcard) { + ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); + write_state->wildcard = true; + } else { + ecs_map_set(write_state->components, component, &value); + } +} + +static +void reset_write_state( + write_state_t *write_state) +{ + ecs_map_clear(write_state->components); + write_state->wildcard = false; +} + +static +int32_t get_any_write_state( + write_state_t *write_state) +{ + if (write_state->wildcard) { + return WriteToStage; + } + + ecs_map_iter_t it = ecs_map_iter(write_state->components); + int32_t *elem; + while ((elem = ecs_map_next(&it, int32_t, NULL))) { + if (*elem == WriteToStage) { + return WriteToStage; + } + } + + return 0; +} + +static +bool check_term_component( + ecs_term_t *term, + bool is_active, + ecs_entity_t component, + write_state_t *write_state) +{ + int32_t state = get_write_state(write_state->components, component); + + ecs_term_id_t *subj = &term->args[0]; + + if ((subj->set.mask & EcsSelf) && subj->entity == EcsThis && term->oper != EcsNot) { + switch(term->inout) { + case EcsInOutDefault: + case EcsInOut: + case EcsIn: + if (state == WriteToStage || write_state->wildcard) { + return true; + } + // fall through + case EcsOut: + if (is_active && term->inout != EcsIn) { + set_write_state(write_state, component, WriteToMain); + } + }; + } else if (!subj->entity || term->oper == EcsNot) { + bool needs_merge = false; + + switch(term->inout) { + case EcsInOutDefault: + case EcsIn: + case EcsInOut: + if (state == WriteToStage) { + needs_merge = true; + } + if (component == EcsWildcard) { + if (get_any_write_state(write_state) == WriteToStage) { + needs_merge = true; + } + } + break; + default: + break; + }; + + switch(term->inout) { + case EcsInOutDefault: + if (!(subj->set.mask & EcsSelf) || !subj->entity || + subj->entity != EcsThis) + { + break; + } + // fall through + case EcsInOut: + case EcsOut: + if (is_active) { + set_write_state(write_state, component, WriteToStage); + } + break; + default: + break; + }; + + if (needs_merge) { + return true; + } + } + + return false; +} + +static +bool check_term( + ecs_term_t *term, + bool is_active, + write_state_t *write_state) +{ + if (term->oper != EcsOr) { + return check_term_component( + term, is_active, term->id, write_state); + } + + return false; +} + +static +bool build_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + EcsPipelineQuery *pq) +{ + (void)pipeline; + + ecs_query_iter(pq->query); + + if (pq->match_count == pq->query->match_count) { + /* No need to rebuild the pipeline */ + return false; + } + + ecs_trace_2("rebuilding pipeline #[green]%s", + ecs_get_name(world, pipeline)); + + world->stats.pipeline_build_count_total ++; + + write_state_t ws = { + .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), + .wildcard = false + }; + + ecs_pipeline_op_t *op = NULL; + ecs_vector_t *ops = NULL; + ecs_query_t *query = pq->build_query; + + if (pq->ops) { + ecs_vector_free(pq->ops); + } + + /* Iterate systems in pipeline, add ops for running / merging */ + ecs_iter_t it = ecs_query_iter(query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + + int i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *q = sys[i].query; + if (!q) { + continue; + } + + bool needs_merge = false; + bool is_active = !ecs_has_id( + world, it.entities[i], EcsInactive); + + ecs_term_t *terms = q->filter.terms; + int32_t t, term_count = q->filter.term_count; + for (t = 0; t < term_count; t ++) { + needs_merge |= check_term(&terms[t], is_active, &ws); + } + + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + reset_write_state(&ws); + 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) { + for (t = 0; t < term_count; t ++) { + needs_merge |= check_term(&terms[t], 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_vector_add(&ops, ecs_pipeline_op_t); + op->count = 0; + } + + /* Don't increase count for inactive systems, as they are ignored by + * the query used to run the pipeline. */ + if (is_active) { + op->count ++; + } + } + } + + ecs_map_free(ws.components); + + /* Force sort of query as this could increase the match_count */ + pq->match_count = pq->query->match_count; + pq->ops = ops; + + return true; +} + +static +int32_t iter_reset( + const EcsPipelineQuery *pq, + ecs_iter_t *iter_out, + ecs_pipeline_op_t **op_out, + ecs_entity_t move_to) +{ + ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; + + *iter_out = ecs_query_iter(pq->query); + while (ecs_query_next(iter_out)) { + int32_t i; + for(i = 0; i < iter_out->count; i ++) { + ecs_entity_t e = iter_out->entities[i]; + + ran_since_merge ++; + if (ran_since_merge == op->count) { + ran_since_merge = 0; + op ++; + } + + if (e == move_to) { + *op_out = op; + return i; + } + } + } + + ecs_abort(ECS_UNSUPPORTED, NULL); + + return -1; +} + +int32_t ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); + + /* 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) { + flecs_eval_component_monitors(world); + } + + bool added = false; + EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, &added); + ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + build_pipeline(world, pipeline, pq); + + return ecs_vector_count(pq->ops); +} + +void ecs_pipeline_run( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); + + if (!pipeline) { + pipeline = world->pipeline; + } + + /* If the world is passed to ecs_pipeline_run, the function will take care + * of staging, so the world should not be in staged mode when called. */ + if (world->magic == ECS_WORLD_MAGIC) { + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + + /* Forward to worker_progress. This function handles staging, threading + * and synchronization across workers. */ + ecs_workers_progress(world, pipeline, delta_time); + return; + + /* If a stage is passed, the function could be ran from a worker thread. In + * that case the main thread should manage staging, and staging should be + * enabled. */ + } else { + ecs_assert(world->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + } + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *ops = pq->ops; + ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + int32_t ran_since_merge = 0; + + int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); + int32_t stage_count = ecs_get_stage_count(world); + + ecs_worker_begin(stage->thread_ctx); + + ecs_iter_t it = ecs_query_iter(pq->query); + while (ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + + int32_t i; + for(i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + + ecs_run_intern(world, stage, e, &sys[i], stage_index, stage_count, + delta_time, 0, 0, NULL, NULL); + + ran_since_merge ++; + world->stats.systems_ran_frame ++; + + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + + /* If the set of matched systems changed as a result of the + * merge, we have to reset the iterator and move it to our + * current position (system). If there are a lot of systems + * in the pipeline this can be an expensive operation, but + * should happen infrequently. */ + if (ecs_worker_sync(stage->thread_ctx)) { + i = iter_reset(pq, &it, &op, e); + op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); + sys = ecs_term(&it, EcsSystem, 1); + } + } + } + } + + ecs_worker_end(stage->thread_ctx); +} + +static +void add_pipeline_tags_to_sig( + ecs_world_t *world, + ecs_term_t *terms, + ecs_type_t type) +{ + (void)world; + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + for (i = 0; i < count; i ++) { + terms[i] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsOr, + .pred.entity = entities[i], + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + } +} + +static +ecs_query_t* build_pipeline_query( + ecs_world_t *world, + ecs_entity_t pipeline, + const char *name, + bool with_inactive) +{ + const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t type_count = ecs_vector_count(type_ptr->normalized); + int32_t term_count = 2; + + if (with_inactive) { + term_count ++; + } + + ecs_term_t *terms = ecs_os_malloc( + (type_count + term_count) * ECS_SIZEOF(ecs_term_t)); + + terms[0] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsAnd, + .pred.entity = ecs_id(EcsSystem), + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + + terms[1] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsNot, + .pred.entity = EcsDisabledIntern, + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + + if (with_inactive) { + terms[2] = (ecs_term_t){ + .inout = EcsIn, + .oper = EcsNot, + .pred.entity = EcsInactive, + .args[0] = { + .entity = EcsThis, + .set.mask = EcsSelf | EcsSuperSet + } + }; + } + + add_pipeline_tags_to_sig(world, &terms[term_count], type_ptr->normalized); + + ecs_query_t *result = ecs_query_init(world, &(ecs_query_desc_t){ + .filter = { + .name = name, + .terms_buffer = terms, + .terms_buffer_count = term_count + type_count + }, + .order_by = compare_entity, + .group_by = group_by_phase, + .group_by_id = pipeline + }); + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_os_free(terms); + + return result; +} + +static +void EcsOnUpdatePipeline( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + ecs_entity_t *entities = it->entities; + + int32_t i; + for (i = it->count - 1; i >= 0; i --) { + ecs_entity_t pipeline = entities[i]; + +#ifndef NDEBUG + ecs_trace_1("pipeline #[green]%s#[normal] created", + ecs_get_name(world, pipeline)); +#endif + ecs_log_push(); + + /* Build signature for pipeline quey that matches EcsSystems, has the + * pipeline phases as OR columns, and ignores systems with EcsInactive + * and EcsDisabledIntern. Note that EcsDisabled is automatically ignored + * by the regular query matching */ + ecs_query_t *query = build_pipeline_query( + world, pipeline, "BuiltinPipelineQuery", true); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Build signature for pipeline build query. The build query includes + * systems that are inactive, as an inactive system may become active as + * a result of another system, and as a result the correct merge + * operations need to be put in place. */ + ecs_query_t *build_query = build_pipeline_query( + world, pipeline, "BuiltinPipelineBuildQuery", false); + ecs_assert(build_query != NULL, ECS_INTERNAL_ERROR, NULL); + + bool added = false; + EcsPipelineQuery *pq = ecs_get_mut( + world, pipeline, EcsPipelineQuery, &added); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + if (added) { + /* Should not modify pipeline after it has been used */ + ecs_assert(pq->ops == NULL, ECS_INVALID_OPERATION, NULL); + + if (pq->query) { + ecs_query_fini(pq->query); + } + if (pq->build_query) { + ecs_query_fini(pq->build_query); + } + } + + pq->query = query; + pq->build_query = build_query; + pq->match_count = -1; + pq->ops = NULL; + + ecs_log_pop(); + } +} + +/* -- Public API -- */ + +bool ecs_progress( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + float delta_time = ecs_frame_begin(world, user_delta_time); + + ecs_pipeline_run(world, 0, delta_time); + + ecs_frame_end(world); + + return !world->should_quit; +} + +void ecs_set_time_scale( + ecs_world_t *world, + FLECS_FLOAT scale) +{ + world->stats.time_scale = scale; +} + +void ecs_reset_clock( + ecs_world_t *world) +{ + world->stats.world_time_total = 0; + world->stats.world_time_total_raw = 0; +} + +void ecs_deactivate_systems( + ecs_world_t *world) +{ + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t pipeline = world->pipeline; + const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Iterate over all systems, add EcsInvalid tag if queries aren't matched + * with any tables */ + ecs_iter_t it = ecs_query_iter(pq->build_query); + + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_none(world, &world->stage); + + while( ecs_query_next(&it)) { + EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_query_t *query = sys[i].query; + if (query) { + if (!ecs_vector_count(query->tables)) { + ecs_add_id(world, it.entities[i], EcsInactive); + } + } + } + } + + flecs_defer_flush(world, &world->stage); +} + +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert( ecs_get(world, pipeline, EcsPipelineQuery) != NULL, + ECS_INVALID_PARAMETER, NULL); + + world->pipeline = pipeline; +} + +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +} + +/* -- Module implementation -- */ + +static +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) +{ + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); + } +} + +void FlecsPipelineImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsPipeline); + + ECS_IMPORT(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_tag(world, EcsPipeline); + flecs_bootstrap_component(world, EcsPipelineQuery); + + /* Phases of the builtin pipeline are regular entities. Names are set so + * they can be resolved by type expressions. */ + flecs_bootstrap_tag(world, EcsPreFrame); + flecs_bootstrap_tag(world, EcsOnLoad); + flecs_bootstrap_tag(world, EcsPostLoad); + flecs_bootstrap_tag(world, EcsPreUpdate); + flecs_bootstrap_tag(world, EcsOnUpdate); + flecs_bootstrap_tag(world, EcsOnValidate); + flecs_bootstrap_tag(world, EcsPostUpdate); + flecs_bootstrap_tag(world, EcsPreStore); + flecs_bootstrap_tag(world, EcsOnStore); + flecs_bootstrap_tag(world, EcsPostFrame); + + ECS_TYPE_IMPL(EcsPipelineQuery); + + /* Set ctor and dtor for PipelineQuery */ + ecs_set(world, ecs_id(EcsPipelineQuery), EcsComponentLifecycle, { + .ctor = ecs_ctor(EcsPipelineQuery), + .dtor = ecs_dtor(EcsPipelineQuery) + }); + + /* When the Pipeline tag is added a pipeline will be created */ + ECS_SYSTEM(world, EcsOnUpdatePipeline, EcsOnSet, Pipeline, Type); + + /* Create the builtin pipeline */ + world->pipeline = ecs_type_init(world, &(ecs_type_desc_t){ + .entity = { + .name = "BuiltinPipeline", + .add = {EcsPipeline} + }, + .ids = { + EcsPreFrame, EcsOnLoad, EcsPostLoad, EcsPreUpdate, EcsOnUpdate, + EcsOnValidate, EcsPostUpdate, EcsPreStore, EcsOnStore, EcsPostFrame + } + }); + + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} + +#endif diff --git a/fggl/ecs2/flecs/src/modules/pipeline/pipeline.h b/fggl/ecs2/flecs/src/modules/pipeline/pipeline.h new file mode 100644 index 0000000000000000000000000000000000000000..68e4c74bc1aaef521b3a4afa923f678c6cb29200 --- /dev/null +++ b/fggl/ecs2/flecs/src/modules/pipeline/pipeline.h @@ -0,0 +1,67 @@ +#ifndef FLECS_PIPELINE_PRIVATE_H +#define FLECS_PIPELINE_PRIVATE_H + +#include "../../private_api.h" +#include "../system/system.h" +#include "flecs/modules/pipeline.h" + +/** Instruction data for pipeline. + * This type is the element type in the "ops" vector of a pipeline and contains + * information about the set of systems that need to be ran before a merge. */ +typedef struct ecs_pipeline_op_t { + int32_t count; /**< Number of systems to run before merge */ +} ecs_pipeline_op_t; + +typedef struct EcsPipelineQuery { + ecs_query_t *query; + ecs_query_t *build_query; + int32_t match_count; + ecs_vector_t *ops; +} EcsPipelineQuery; + +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// + +/** Update a pipeline (internal function). + * Before running a pipeline, it must be updated. During this update phase + * all systems in the pipeline are collected, ordered and sync points are + * inserted where necessary. This operation may only be called when staging is + * disabled. + * + * Because multiple threads may run a pipeline, preparing the pipeline must + * happen synchronously, which is why this function is separate from + * ecs_pipeline_run. Not running the prepare step may cause systems to not get + * ran, or ran in the wrong order. + * + * If 0 is provided for the pipeline id, the default pipeline will be ran (this + * is either the builtin pipeline or the pipeline set with set_pipeline()). + * + * @param world The world. + * @param pipeline The pipeline to run. + * @return The number of elements in the pipeline. + */ +int32_t ecs_pipeline_update( + ecs_world_t *world, + ecs_entity_t pipeline, + bool start_of_frame); + +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// + +void ecs_worker_begin( + ecs_world_t *world); + +bool ecs_worker_sync( + ecs_world_t *world); + +void ecs_worker_end( + ecs_world_t *world); + +void ecs_workers_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time); + +#endif diff --git a/fggl/ecs2/flecs/src/modules/pipeline/worker.c b/fggl/ecs2/flecs/src/modules/pipeline/worker.c new file mode 100644 index 0000000000000000000000000000000000000000..99e884ad0fea9928b598f766c929973f4748c4c6 --- /dev/null +++ b/fggl/ecs2/flecs/src/modules/pipeline/worker.c @@ -0,0 +1,315 @@ +#include "flecs.h" + +#ifdef FLECS_PIPELINE + +#include "pipeline.h" + +/* Worker thread */ +static +void* worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; + + /* Start worker thread, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; + + if (!world->quit_workers) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } + + ecs_os_mutex_unlock(world->sync_mutex); + + while (!world->quit_workers) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + + ecs_pipeline_run( + (ecs_world_t*)stage, + world->pipeline, + world->stats.delta_time); + + ecs_set_scope((ecs_world_t*)stage, old_scope); + } + + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); + + return NULL; +} + +/* Start threads */ +static +void start_workers( + ecs_world_t *world, + int32_t threads) +{ + ecs_set_stages(world, threads); + + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + + int32_t i; + for (i = 0; i < threads; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(stage->magic == ECS_STAGE_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_get(world->worker_stages, ecs_stage_t, i); + stage->thread = ecs_os_thread_new(worker, stage); + ecs_assert(stage->thread != 0, ECS_THREAD_ERROR, NULL); + } +} + +/* Wait until all workers are running */ +static +void wait_for_workers( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + bool wait = true; + + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == stage_count) { + wait = false; + } + ecs_os_mutex_unlock(world->sync_mutex); + } while (wait); +} + +/* Synchronize worker threads */ +static +void sync_worker( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + + /* Signal that thread is waiting */ + ecs_os_mutex_lock(world->sync_mutex); + if (++ world->workers_waiting == stage_count) { + /* Only signal main thread when all threads are waiting */ + ecs_os_cond_signal(world->sync_cond); + } + + /* Wait until main thread signals that thread can continue */ + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Wait until all threads are waiting on sync point */ +static +void wait_for_sync( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_waiting != stage_count) { + ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + } + + /* We should have been signalled unless all workers are waiting on sync */ + ecs_assert(world->workers_waiting == stage_count, + ECS_INTERNAL_ERROR, NULL); + + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Signal workers that they can start/resume work */ +static +void signal_workers( + ecs_world_t *world) +{ + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/** Stop worker threads */ +static +bool ecs_stop_threads( + ecs_world_t *world) +{ + 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. */ + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + if (stage->thread) { + threads_active = true; + break; + } + stage->thread = 0; + }); + + /* If no threads are active, just return */ + if (!threads_active) { + return false; + } + + /* Make sure all threads are running, to ensure they catch the signal */ + wait_for_workers(world); + + /* Signal threads should quit */ + world->quit_workers = true; + signal_workers(world); + + /* Join all threads with main */ + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + ecs_os_thread_join(stage->thread); + stage->thread = 0; + }); + + world->quit_workers = false; + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); + + /* Deinitialize stages */ + ecs_set_stages(world, 0); + + return true; +} + +/* -- Private functions -- */ + +void ecs_worker_begin( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + if (stage_count == 1) { + ecs_staging_begin(world); + } +} + +bool ecs_worker_sync( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + + int32_t build_count = world->stats.pipeline_build_count_total; + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + /* If there are no threads, merge in place */ + if (stage_count == 1) { + ecs_staging_end(world); + ecs_pipeline_update(world, world->pipeline, false); + ecs_staging_begin(world); + + /* Synchronize all workers. The last worker to reach the sync point will + * signal the main thread, which will perform the merge. */ + } else { + sync_worker(world); + } + + return world->stats.pipeline_build_count_total != build_count; +} + +void ecs_worker_end( + ecs_world_t *world) +{ + flecs_stage_from_world(&world); + + int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + + /* If there are no threads, merge in place */ + if (stage_count == 1) { + ecs_staging_end(world); + + /* Synchronize all workers. The last worker to reach the sync point will + * signal the main thread, which will perform the merge. */ + } else { + sync_worker(world); + } +} + +void ecs_workers_progress( + ecs_world_t *world, + ecs_entity_t pipeline, + FLECS_FLOAT delta_time) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + int32_t stage_count = ecs_get_stage_count(world); + + ecs_time_t start = {0}; + if (world->measure_frame_time) { + ecs_time_measure(&start); + } + + if (stage_count == 1) { + ecs_pipeline_update(world, pipeline, true); + ecs_entity_t old_scope = ecs_set_scope(world, 0); + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_pipeline_run(stage, pipeline, delta_time); + ecs_set_scope(world, old_scope); + } else { + int32_t i, sync_count = ecs_pipeline_update(world, pipeline, true); + + /* Make sure workers are running and ready */ + wait_for_workers(world); + + /* Synchronize n times for each op in the pipeline */ + for (i = 0; i < sync_count; i ++) { + ecs_staging_begin(world); + + /* Signal workers that they should start running systems */ + world->workers_waiting = 0; + signal_workers(world); + + /* Wait until all workers are waiting on sync point */ + wait_for_sync(world); + + /* Merge */ + ecs_staging_end(world); + + int32_t update_count; + if ((update_count = ecs_pipeline_update(world, pipeline, false))) { + /* The number of operations in the pipeline could have changed + * as result of the merge */ + sync_count = update_count; + } + } + } + + if (world->measure_frame_time) { + world->stats.system_time_total += (FLECS_FLOAT)ecs_time_measure(&start); + } +} + + +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + + int32_t stage_count = ecs_get_stage_count(world); + + if (!world->arg_threads && stage_count != threads) { + /* Stop existing threads */ + if (stage_count > 1) { + if (ecs_stop_threads(world)) { + ecs_os_cond_free(world->worker_cond); + ecs_os_cond_free(world->sync_cond); + ecs_os_mutex_free(world->sync_mutex); + } + } + + /* Start threads if number of threads > 1 */ + if (threads > 1) { + world->worker_cond = ecs_os_cond_new(); + world->sync_cond = ecs_os_cond_new(); + world->sync_mutex = ecs_os_mutex_new(); + start_workers(world, threads); + } + } +} + +#endif diff --git a/fggl/ecs2/flecs/src/modules/system/system.c b/fggl/ecs2/flecs/src/modules/system/system.c new file mode 100644 index 0000000000000000000000000000000000000000..e4e5ed101093aa68c6404dda44b296e8b91ed4a1 --- /dev/null +++ b/fggl/ecs2/flecs/src/modules/system/system.c @@ -0,0 +1,780 @@ +#include "flecs.h" + +#ifdef FLECS_SYSTEM + +#include "../../private_api.h" +#include "system.h" + +/* Global type variables */ +ECS_TYPE_DECL(EcsComponentLifecycle); +ECS_TYPE_DECL(EcsSystem); +ECS_TYPE_DECL(EcsTickSource); + +static +ecs_on_demand_in_t* get_in_component( + ecs_map_t *component_map, + ecs_entity_t component) +{ + ecs_on_demand_in_t *in = ecs_map_get( + component_map, ecs_on_demand_in_t, component); + if (!in) { + ecs_on_demand_in_t in_value = {0}; + ecs_map_set(component_map, component, &in_value); + in = ecs_map_get(component_map, ecs_on_demand_in_t, component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + } + + return in; +} + +static +void activate_in_columns( + ecs_world_t *world, + ecs_query_t *query, + ecs_map_t *component_map, + bool activate) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + if (terms[i].inout == EcsIn) { + ecs_on_demand_in_t *in = get_in_component( + component_map, terms[i].id); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + + in->count += activate ? 1 : -1; + + ecs_assert(in->count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* If this is the first system that registers the in component, walk + * over all already registered systems to enable them */ + if (in->systems && + ((activate && in->count == 1) || + (!activate && !in->count))) + { + ecs_on_demand_out_t **out = ecs_vector_first( + in->systems, ecs_on_demand_out_t*); + int32_t s, in_count = ecs_vector_count(in->systems); + + for (s = 0; s < in_count; s ++) { + /* Increase the count of the system with the out params */ + out[s]->count += activate ? 1 : -1; + + /* If this is the first out column that is requested from + * the OnDemand system, enable it */ + if (activate && out[s]->count == 1) { + ecs_remove_id(world, out[s]->system, EcsDisabledIntern); + } else if (!activate && !out[s]->count) { + ecs_add_id(world, out[s]->system, EcsDisabledIntern); + } + } + } + } + } +} + +static +void register_out_column( + ecs_map_t *component_map, + ecs_entity_t component, + ecs_on_demand_out_t *on_demand_out) +{ + ecs_on_demand_in_t *in = get_in_component(component_map, component); + ecs_assert(in != NULL, ECS_INTERNAL_ERROR, NULL); + + on_demand_out->count += in->count; + ecs_on_demand_out_t **elem = ecs_vector_add(&in->systems, ecs_on_demand_out_t*); + *elem = on_demand_out; +} + +static +void register_out_columns( + ecs_world_t *world, + ecs_entity_t system, + EcsSystem *system_data) +{ + ecs_query_t *query = system_data->query; + ecs_term_t *terms = query->filter.terms; + int32_t out_count = 0, i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + if (terms[i].inout == EcsOut) { + if (!system_data->on_demand) { + system_data->on_demand = ecs_os_malloc(sizeof(ecs_on_demand_out_t)); + ecs_assert(system_data->on_demand != NULL, ECS_OUT_OF_MEMORY, NULL); + + system_data->on_demand->system = system; + system_data->on_demand->count = 0; + } + + /* If column operator is NOT and the inout kind is [out], the system + * explicitly states that it will create the component (it is not + * there, yet it is an out column). In this case it doesn't make + * sense to wait until [in] terms get activated (matched with + * entities) since the component is not there yet. Therefore add it + * to the on_enable_components list, so this system will be enabled + * when a [in] column is enabled, rather than activated */ + ecs_map_t *component_map; + if (terms[i].oper == EcsNot) { + component_map = world->on_enable_components; + } else { + component_map = world->on_activate_components; + } + + register_out_column( + component_map, terms[i].id, + system_data->on_demand); + + out_count ++; + } + } + + /* If there are no out terms in the on-demand system, the system will + * never be enabled */ + ecs_assert(out_count != 0, ECS_NO_OUT_COLUMNS, ecs_get_name(world, system)); +} + +static +void invoke_status_action( + ecs_world_t *world, + ecs_entity_t system, + const EcsSystem *system_data, + ecs_system_status_t status) +{ + ecs_system_status_action_t action = system_data->status_action; + if (action) { + action(world, system, status, system_data->status_ctx); + } +} + +/* Invoked when system becomes active or inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data) +{ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + if (activate) { + /* If activating system, ensure that it doesn't have the Inactive tag. + * Systems are implicitly activated so they are kept out of the main + * loop as long as they aren't used. They are not implicitly deactivated + * to prevent overhead in case of oscillating app behavior. + * After activation, systems that aren't matched with anything can be + * deactivated again by explicitly calling ecs_deactivate_systems. + */ + ecs_remove_id(world, system, EcsInactive); + } + + if (!system_data) { + system_data = ecs_get(world, system, EcsSystem); + } + if (!system_data || !system_data->query) { + return; + } + + if (!activate) { + if (ecs_has_id(world, system, EcsDisabled) || + ecs_has_id(world, system, EcsDisabledIntern)) + { + if (!ecs_vector_count(system_data->query->tables)) { + /* If deactivating a disabled system that isn't matched with + * any active tables, there is nothing to deactivate. */ + return; + } + } + } + + /* If system contains in columns, signal that they are now in use */ + activate_in_columns( + world, system_data->query, world->on_activate_components, activate); + + /* Invoke system status action */ + invoke_status_action(world, system, system_data, + activate ? EcsSystemActivated : EcsSystemDeactivated); + + ecs_trace_2("system #[green]%s#[reset] %s", + ecs_get_name(world, system), + activate ? "activated" : "deactivated"); +} + +/* Actually enable or disable system */ +static +void ecs_enable_system( + ecs_world_t *world, + ecs_entity_t system, + EcsSystem *system_data, + bool enabled) +{ + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + ecs_query_t *query = system_data->query; + if (!query) { + return; + } + + if (ecs_vector_count(query->tables)) { + /* Only (de)activate system if it has non-empty tables. */ + ecs_system_activate(world, system, enabled, system_data); + system_data = ecs_get_mut(world, system, EcsSystem, NULL); + } + + /* Enable/disable systems that trigger on [in] enablement */ + activate_in_columns( + world, + query, + world->on_enable_components, + enabled); + + /* Invoke action for enable/disable status */ + invoke_status_action( + world, system, system_data, + enabled ? EcsSystemEnabled : EcsSystemDisabled); +} + +/* -- Public API -- */ + +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + const EcsType *type_ptr = ecs_get( world, entity, EcsType); + if (type_ptr) { + /* If entity is a type, disable all entities in the type */ + ecs_vector_each(type_ptr->normalized, ecs_entity_t, e, { + ecs_enable(world, *e, enabled); + }); + } else { + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); + } + } +} + +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param) +{ + FLECS_FLOAT time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; + + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; + } + + if (tick_source) { + const EcsTickSource *tick = ecs_get( + world, tick_source, EcsTickSource); + + if (tick) { + time_elapsed = tick->time_elapsed; + + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } + } else { + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; + } + } + + ecs_time_t time_start; + bool measure_time = world->measure_system_time; + if (measure_time) { + ecs_os_get_time(&time_start); + } + + ecs_defer_begin(stage->thread_ctx); + + /* Prepare the query iterator */ + ecs_iter_t it = ecs_query_iter_page(system_data->query, offset, limit); + it.world = stage->thread_ctx; + it.system = system; + it.self = system_data->self; + it.delta_time = delta_time; + it.delta_system_time = time_elapsed; + it.world_time = world->stats.world_time_total; + it.frame_offset = offset; + it.param = param; + it.ctx = system_data->ctx; + it.binding_ctx = system_data->binding_ctx; + + ecs_iter_action_t action = system_data->action; + + /* If no filter is provided, just iterate tables & invoke action */ + if (stage_count <= 1) { + while (ecs_query_next_w_filter(&it, filter)) { + action(&it); + } + } else { + while (ecs_query_next_worker(&it, stage_current, stage_count)) { + action(&it); + } + } + + ecs_defer_end(stage->thread_ctx); + + if (measure_time) { + system_data->time_spent += (FLECS_FLOAT)ecs_time_measure(&time_start); + } + + system_data->invoke_count ++; + + return it.interrupted_by; +} + +/* -- Public API -- */ + +ecs_entity_t ecs_run_w_filter( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); + + return ecs_run_intern( + world, stage, system, system_data, 0, 0, delta_time, offset, limit, + filter, param); +} + +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + + EcsSystem *system_data = (EcsSystem*)ecs_get( + world, system, EcsSystem); + assert(system_data != NULL); + + return ecs_run_intern( + world, stage, system, system_data, stage_current, stage_count, + delta_time, 0, 0, NULL, param); +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + FLECS_FLOAT delta_time, + void *param) +{ + return ecs_run_w_filter(world, system, delta_time, 0, 0, NULL, param); +} + +void flecs_run_monitor( + ecs_world_t *world, + ecs_matched_query_t *monitor, + ecs_ids_t *components, + int32_t row, + int32_t count, + ecs_entity_t *entities) +{ + ecs_query_t *query = monitor->query; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t system = query->system; + const EcsSystem *system_data = ecs_get(world, system, EcsSystem); + ecs_assert(system_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!system_data->action) { + return; + } + + ecs_iter_t it = {0}; + flecs_query_set_iter( world, query, &it, + monitor->matched_table_index, row, count); + + it.world = world; + it.triggered_by = components; + it.is_valid = true; + it.ctx = system_data->ctx; + it.binding_ctx = system_data->binding_ctx; + + if (entities) { + it.entities = entities; + } + + it.system = system; + system_data->action(&it); +} + +ecs_query_t* ecs_get_system_query( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsQuery *q = ecs_get(world, system, EcsQuery); + if (q) { + return q->query; + } else { + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->query; + } else { + return NULL; + } + } +} + +void* ecs_get_system_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->ctx; + } else { + return NULL; + } +} + +void* ecs_get_system_binding_ctx( + const ecs_world_t *world, + ecs_entity_t system) +{ + const EcsSystem *s = ecs_get(world, system, EcsSystem); + if (s) { + return s->binding_ctx; + } else { + return NULL; + } +} + +/* Generic constructor to initialize a component to 0 */ +static +void sys_ctor_init_zero( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)world; + (void)component; + (void)entities; + (void)ctx; + memset(ptr, 0, size * (size_t)count); +} + +/* System destructor */ +static +void ecs_colsystem_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entities, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + (void)component; + (void)ctx; + (void)size; + + EcsSystem *system_data = ptr; + + int i; + for (i = 0; i < count; i ++) { + EcsSystem *system = &system_data[i]; + ecs_entity_t e = entities[i]; + + /* Invoke Deactivated action for active systems */ + if (system->query && ecs_vector_count(system->query->tables)) { + invoke_status_action(world, e, ptr, EcsSystemDeactivated); + } + + /* Invoke Disabled action for enabled systems */ + if (!ecs_has_id(world, e, EcsDisabled) && + !ecs_has_id(world, e, EcsDisabledIntern)) + { + invoke_status_action(world, e, ptr, EcsSystemDisabled); + } + + ecs_os_free(system->on_demand); + + if (system->ctx_free) { + system->ctx_free(system->ctx); + } + + if (system->status_ctx_free) { + system->status_ctx_free(system->status_ctx); + } + + if (system->binding_ctx_free) { + system->binding_ctx_free(system->binding_ctx); + } + + if (system->query) { + ecs_query_fini(system->query); + } + } +} + +/* Disable system when EcsDisabled is added */ +static +void DisableSystem( + ecs_iter_t *it) +{ + EcsSystem *system_data = ecs_term(it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_enable_system( + it->world, it->entities[i], &system_data[i], false); + } +} + +/* Enable system when EcsDisabled is removed */ +static +void EnableSystem( + ecs_iter_t *it) +{ + EcsSystem *system_data = ecs_term(it, EcsSystem, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_enable_system( + it->world, it->entities[i], &system_data[i], true); + } +} + +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_FROM_WORKER, NULL); + ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t result = ecs_entity_init(world, &desc->entity); + if (!result) { + return 0; + } + + bool added = false; + EcsSystem *system = ecs_get_mut(world, result, EcsSystem, &added); + if (added) { + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + memset(system, 0, sizeof(EcsSystem)); + + ecs_query_desc_t query_desc = desc->query; + query_desc.filter.name = desc->entity.name; + query_desc.system = result; + + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, result); + return 0; + } + + /* Re-obtain pointer, as query may have added components */ + system = ecs_get_mut(world, result, EcsSystem, &added); + ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); + + /* Prevent the system from moving while we're initializing */ + ecs_defer_begin(world); + + system->entity = result; + system->query = query; + + system->action = desc->callback; + system->status_action = desc->status_callback; + + system->self = desc->self; + system->ctx = desc->ctx; + system->status_ctx = desc->status_ctx; + system->binding_ctx = desc->binding_ctx; + + system->ctx_free = desc->ctx_free; + system->status_ctx_free = desc->status_ctx_free; + system->binding_ctx_free = desc->binding_ctx_free; + + system->tick_source = desc->tick_source; + + /* If tables have been matched with this system it is active, and we + * should activate the in terms, if any. This will ensure that any + * OnDemand systems get enabled. */ + if (ecs_vector_count(query->tables)) { + ecs_system_activate(world, result, true, system); + } else { + /* If system isn't matched with any tables, mark it as inactive. This + * causes it to be ignored by the main loop. When the system matches + * with a table it will be activated. */ + ecs_add_id(world, result, EcsInactive); + } + + /* If system is enabled, trigger enable components */ + activate_in_columns(world, query, world->on_enable_components, true); + + /* If the query has a OnDemand system tag, register its [out] terms */ + if (ecs_has_id(world, result, EcsOnDemand)) { + register_out_columns(world, result, system); + ecs_assert(system->on_demand != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If there are no systems currently interested in any of the [out] + * terms of the on demand system, disable it */ + if (!system->on_demand->count) { + ecs_add_id(world, result, EcsDisabledIntern); + } + } + + if (!ecs_has_id(world, result, EcsDisabled)) { + /* If system is already enabled, generate enable status. The API + * should guarantee that it exactly matches enable-disable + * notifications and activate-deactivate notifications. */ + invoke_status_action(world, result, system, EcsSystemEnabled); + + /* If column system has active (non-empty) tables, also generate the + * activate status. */ + if (ecs_vector_count(system->query->tables)) { + invoke_status_action(world, result, system, EcsSystemActivated); + } + } + + if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { +#ifdef FLECS_TIMER + if (desc->interval != 0) { + ecs_set_interval(world, result, desc->interval); + } + + if (desc->rate) { + ecs_set_rate(world, result, desc->rate, desc->tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, result, desc->tick_source); + } +#else + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif + } + + ecs_modified(world, result, EcsSystem); + + ecs_trace_1("system #[green]%s#[reset] created with #[red]%s", + ecs_get_name(world, result), query->filter.expr); + + ecs_defer_end(world); + } else { + const char *expr_desc = desc->query.filter.expr; + const char *expr_sys = system->query->filter.expr; + + /* Only check expression if it's set */ + if (expr_desc) { + if (expr_sys && !strcmp(expr_sys, "0")) expr_sys = NULL; + if (expr_desc && !strcmp(expr_desc, "0")) expr_desc = NULL; + + if (expr_sys && expr_desc) { + if (strcmp(expr_sys, expr_desc)) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } else { + if (expr_sys != expr_desc) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } + + /* If expr_desc is not set, and this is an existing system, don't throw + * an error because we could be updating existing parameters of the + * system such as the context or system callback. However, if no + * entity handle was provided, we have to assume that the application is + * trying to redeclare the system. */ + } else if (!existing) { + if (expr_sys) { + ecs_abort(ECS_ALREADY_DEFINED, desc->entity.name); + } + } + + /* Override the existing callback or context */ + if (desc->callback) { + system->action = desc->callback; + } + if (desc->ctx) { + system->ctx = desc->ctx; + } + if (desc->binding_ctx) { + system->binding_ctx = desc->binding_ctx; + } + } + + return result; +} + +void FlecsSystemImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); + + flecs_bootstrap_tag(world, EcsOnAdd); + flecs_bootstrap_tag(world, EcsOnRemove); + flecs_bootstrap_tag(world, EcsOnSet); + flecs_bootstrap_tag(world, EcsUnSet); + + /* Put following tags in flecs.core so they can be looked up + * without using the flecs.systems prefix. */ + ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore); + flecs_bootstrap_tag(world, EcsDisabledIntern); + flecs_bootstrap_tag(world, EcsInactive); + flecs_bootstrap_tag(world, EcsOnDemand); + flecs_bootstrap_tag(world, EcsMonitor); + ecs_set_scope(world, old_scope); + + ECS_TYPE_IMPL(EcsComponentLifecycle); + ECS_TYPE_IMPL(EcsSystem); + ECS_TYPE_IMPL(EcsTickSource); + + /* Bootstrap ctor and dtor for EcsSystem */ + ecs_set_component_actions_w_id(world, ecs_id(EcsSystem), + &(EcsComponentLifecycle) { + .ctor = sys_ctor_init_zero, + .dtor = ecs_colsystem_dtor + }); + + /* Monitors that trigger when a system is enabled or disabled */ + ECS_SYSTEM(world, DisableSystem, EcsMonitor, + System, Disabled || DisabledIntern, SYSTEM:Hidden); + + ECS_SYSTEM(world, EnableSystem, EcsMonitor, + System, !Disabled, !DisabledIntern, SYSTEM:Hidden); +} + +#endif diff --git a/fggl/ecs2/flecs/src/modules/system/system.h b/fggl/ecs2/flecs/src/modules/system/system.h new file mode 100644 index 0000000000000000000000000000000000000000..0ad6fca599060d586cb7d7f8afe21911b6e9dcc4 --- /dev/null +++ b/fggl/ecs2/flecs/src/modules/system/system.h @@ -0,0 +1,51 @@ +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H + +#include "../../private_api.h" + +typedef struct EcsSystem { + ecs_iter_action_t action; /* Callback to be invoked for matching it */ + + ecs_entity_t entity; /* Entity id of system, used for ordering */ + ecs_query_t *query; /* System query */ + ecs_on_demand_out_t *on_demand; /* Keep track of [out] column refs */ + ecs_system_status_action_t status_action; /* Status action */ + ecs_entity_t tick_source; /* Tick source associated with system */ + + int32_t invoke_count; /* Number of times system is invoked */ + FLECS_FLOAT time_spent; /* Time spent on running system */ + FLECS_FLOAT time_passed; /* Time passed since last invocation */ + + ecs_entity_t self; /* Entity associated with system */ + + void *ctx; /* Userdata for system */ + void *status_ctx; /* User data for status action */ + void *binding_ctx; /* Optional language binding context */ + + ecs_ctx_free_t ctx_free; + ecs_ctx_free_t status_ctx_free; + ecs_ctx_free_t binding_ctx_free; +} EcsSystem; + +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const EcsSystem *system_data); + +/* Internal function to run a system */ +ecs_entity_t ecs_run_intern( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + EcsSystem *system_data, + int32_t stage_current, + int32_t stage_count, + FLECS_FLOAT delta_time, + int32_t offset, + int32_t limit, + const ecs_filter_t *filter, + void *param); + +#endif diff --git a/fggl/ecs2/flecs/src/modules/timer.c b/fggl/ecs2/flecs/src/modules/timer.c new file mode 100644 index 0000000000000000000000000000000000000000..77d2ca9171cf41bb7822619343a1e7a0a23fad51 --- /dev/null +++ b/fggl/ecs2/flecs/src/modules/timer.c @@ -0,0 +1,273 @@ +#include "flecs.h" + +#ifdef FLECS_TIMER + +#include "../private_api.h" +#include "system/system.h" + +ecs_type_t ecs_type(EcsTimer); +ecs_type_t ecs_type(EcsRateFilter); + +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_term(it, EcsTimer, 1); + EcsTickSource *tick_source = ecs_term(it, EcsTickSource, 2); + + 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); + FLECS_FLOAT time_elapsed = timer[i].time + info->delta_time_raw; + FLECS_FLOAT timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + FLECS_FLOAT t = time_elapsed - timeout; + if (t > timeout) { + t = 0; + } + + timer[i].time = t; /* Initialize with remainder */ + tick_source[i].tick = true; + tick_source[i].time_elapsed = time_elapsed; + + 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_term(it, EcsRateFilter, 1); + EcsTickSource *tick_dst = ecs_term(it, EcsTickSource, 2); + + 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_term(it, EcsTickSource, 1); + + /* 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, + FLECS_FLOAT timeout) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } + + return timer; +} + +FLECS_FLOAT ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(timer != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } else { + return 0; + } +} + +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + FLECS_FLOAT interval) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + timer = ecs_set(world, timer, EcsTimer, { + .timeout = interval, + .active = true + }); + + EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = timer; + } + + return timer; +} + +FLECS_FLOAT ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + return 0; + } + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } else { + return 0; + } +} + +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ptr->active = true; + ptr->time = 0; +} + +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ptr->active = false; +} + +ecs_entity_t ecs_set_rate( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + filter = ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); + + EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL); + if (system_data) { + system_data->tick_source = filter; + } + + return filter; +} + +/* Deprecated */ +ecs_entity_t ecs_set_rate_filter( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + return ecs_set_rate(world, filter, rate, source); +} + +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + + EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + system_data->tick_source = tick_source; +} + +void FlecsTimerImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsTimer); + + ECS_IMPORT(world, FlecsPipeline); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsTimer); + flecs_bootstrap_component(world, EcsRateFilter); + + /* Add EcsTickSource to timers and rate filters */ + ECS_SYSTEM(world, AddTickSource, EcsPreFrame, [in] Timer || RateFilter, [out] !flecs.system.TickSource); + + /* Timer handling */ + ECS_SYSTEM(world, ProgressTimers, EcsPreFrame, Timer, flecs.system.TickSource); + + /* Rate filter handling */ + ECS_SYSTEM(world, ProgressRateFilters, EcsPreFrame, [in] RateFilter, [out] flecs.system.TickSource); + + /* TickSource without a timer or rate filter just increases each frame */ + ECS_SYSTEM(world, ProgressTickSource, EcsPreFrame, [out] flecs.system.TickSource, !RateFilter, !Timer); +} + +#endif diff --git a/fggl/ecs2/flecs/src/observer.c b/fggl/ecs2/flecs/src/observer.c new file mode 100644 index 0000000000000000000000000000000000000000..39847ff20fea4585aeee74fd9e33c66f5ec6e4ac --- /dev/null +++ b/fggl/ecs2/flecs/src/observer.c @@ -0,0 +1,187 @@ +#include "private_api.h" + +static +void observer_callback(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_world_t *world = it->world; + + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = it->table; + ecs_type_t type = table->type; + + ecs_iter_t user_it = *it; + user_it.column_count = o->filter.term_count_actual, + user_it.ids = NULL; + user_it.columns = NULL; + user_it.types = NULL; + user_it.subjects = NULL; + user_it.sizes = NULL; + user_it.ptrs = NULL; + + ecs_iter_init(&user_it); + + if (flecs_filter_match_table(world, &o->filter, table, type, + user_it.ids, user_it.columns, user_it.types, user_it.subjects, + user_it.sizes, user_it.ptrs)) + { + ecs_data_t *data = flecs_table_get_data(table); + + user_it.ids[it->term_index] = it->event_id; + + user_it.system = o->entity; + user_it.term_index = it->term_index; + user_it.self = o->self; + user_it.ctx = o->ctx; + user_it.column_count = o->filter.term_count_actual, + user_it.table_columns = data->columns, + o->action(&user_it); + } + + ecs_iter_fini(&user_it); +} + +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + /* If entity is provided, create it */ + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t entity = ecs_entity_init(world, &desc->entity); + + bool added = false; + EcsObserver *comp = ecs_get_mut(world, entity, EcsObserver, &added); + if (added) { + ecs_observer_t *observer = flecs_sparse_add( + world->observers, ecs_observer_t); + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + observer->id = flecs_sparse_last_id(world->observers); + + /* Make writeable copy of filter desc so that we can set name. This will + * make debugging easier, as any error messages related to creating the + * filter will have the name of the observer. */ + ecs_filter_desc_t filter_desc = desc->filter; + filter_desc.name = desc->entity.name; + + /* Parse filter */ + if (ecs_filter_init(world, &observer->filter, &filter_desc)) { + flecs_observer_fini(world, observer); + return 0; + } + + ecs_filter_t *filter = &observer->filter; + + /* Create a trigger for each term in the filter */ + observer->triggers = ecs_os_malloc(ECS_SIZEOF(ecs_entity_t) * + observer->filter.term_count); + + int i; + for (i = 0; i < filter->term_count; i ++) { + const ecs_term_t *terms = filter->terms; + const ecs_term_t *t = &terms[i]; + + if (t->oper == EcsNot || terms[i].args[0].entity != EcsThis) { + /* No need to trigger on components that the entity should not + * have, or on components that are not defined on the entity */ + observer->triggers[i] = 0; + continue; + } + + ecs_trigger_desc_t trigger_desc = { + .term = *t, + .callback = observer_callback, + .ctx = observer, + .binding_ctx = desc->binding_ctx + }; + + ecs_os_memcpy(trigger_desc.events, desc->events, + ECS_SIZEOF(ecs_entity_t) * ECS_TRIGGER_DESC_EVENT_COUNT_MAX); + observer->triggers[i] = ecs_trigger_init(world, &trigger_desc); + } + + observer->action = desc->callback; + observer->self = desc->self; + observer->ctx = desc->ctx; + observer->binding_ctx = desc->binding_ctx; + observer->ctx_free = desc->ctx_free; + observer->binding_ctx_free = desc->binding_ctx_free; + observer->event_count = 0; + ecs_os_memcpy(observer->events, desc->events, + observer->event_count * ECS_SIZEOF(ecs_entity_t)); + observer->entity = entity; + + comp->observer = observer; + } else { + ecs_assert(comp->observer != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If existing entity handle was provided, override existing params */ + if (existing) { + if (desc->callback) { + ((ecs_observer_t*)comp->observer)->action = desc->callback; + } + if (desc->ctx) { + ((ecs_observer_t*)comp->observer)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ((ecs_observer_t*)comp->observer)->binding_ctx = + desc->binding_ctx; + } + } + } + + return entity; +} + +void flecs_observer_fini( + ecs_world_t *world, + ecs_observer_t *observer) +{ + int i, count = observer->filter.term_count; + for (i = 0; i < count; i ++) { + ecs_entity_t trigger = observer->triggers[i]; + if (trigger) { + ecs_delete(world, trigger); + } + } + ecs_os_free(observer->triggers); + + ecs_filter_fini(&observer->filter); + + if (observer->ctx_free) { + observer->ctx_free(observer->ctx); + } + + if (observer->binding_ctx_free) { + observer->binding_ctx_free(observer->binding_ctx); + } + + flecs_sparse_remove(world->observers, observer->id); +} + +void* ecs_get_observer_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsObserver *o = ecs_get(world, observer, EcsObserver); + if (o) { + return o->observer->ctx; + } else { + return NULL; + } +} + +void* ecs_get_observer_binding_ctx( + const ecs_world_t *world, + ecs_entity_t observer) +{ + const EcsObserver *o = ecs_get(world, observer, EcsObserver); + if (o) { + return o->observer->binding_ctx; + } else { + return NULL; + } +} diff --git a/fggl/ecs2/flecs/src/os_api.c b/fggl/ecs2/flecs/src/os_api.c new file mode 100644 index 0000000000000000000000000000000000000000..6157ef1933aad2f17fb41a0aab0b0afd95677549 --- /dev/null +++ b/fggl/ecs2/flecs/src/os_api.c @@ -0,0 +1,326 @@ +#include "private_api.h" + +void ecs_os_api_impl(ecs_os_api_t *api); + +static bool ecs_os_api_initialized = false; +static int ecs_os_api_init_count = 0; + +ecs_os_api_t ecs_os_api; + +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; + } +} + +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_(); + } + } +} + +static +void ecs_log(const char *fmt, va_list args) { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); +} + +static +void ecs_log_error(const char *fmt, va_list args) { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +static +void ecs_log_debug(const char *fmt, va_list args) { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); +} + +static +void ecs_log_warning(const char *fmt, va_list args) { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +void ecs_os_dbg(const char *fmt, ...) { +#ifndef NDEBUG + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_debug_) { + ecs_os_api.log_debug_(fmt, args); + } + va_end(args); +#else + (void)fmt; +#endif +} + +void ecs_os_warn(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_warning_) { + ecs_os_api.log_warning_(fmt, args); + } + va_end(args); +} + +void ecs_os_log(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_) { + ecs_os_api.log_(fmt, args); + } + va_end(args); +} + +void ecs_os_err(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (ecs_os_api.log_error_) { + ecs_os_api.log_error_(fmt, args); + } + va_end(args); +} + +static +void ecs_os_gettime(ecs_time_t *time) +{ + uint64_t now = flecs_os_time_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_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_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_api_realloc_count ++; + } else { + /* If not actually reallocing, treat as malloc */ + ecs_os_api_malloc_count ++; + } + + return realloc(ptr, (size_t)size); +} + +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + 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; + } +} + +/* 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_OS_LINUX) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".so"); +#elif defined(ECS_OS_DARWIN) + ecs_strbuf_appendstr(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&lib, ".dylib"); +#elif defined(ECS_OS_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendstr(&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_appendstr(&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; + } + + flecs_os_time_setup(); + + /* 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.sleep_ = flecs_os_time_sleep; + ecs_os_api.get_time_ = ecs_os_gettime; + + /* Logging */ + ecs_os_api.log_ = ecs_log; + ecs_os_api.log_error_ = ecs_log_error; + ecs_os_api.log_debug_ = ecs_log_debug; + ecs_os_api.log_warning_ = ecs_log_warning; + + /* 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; +} + +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); +} + +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL); +} + +bool ecs_os_has_logging(void) { + return + (ecs_os_api.log_ != NULL) && + (ecs_os_api.log_error_ != NULL) && + (ecs_os_api.log_debug_ != NULL) && + (ecs_os_api.log_warning_ != 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(_MSC_VER) +static char error_str[255]; +#endif + +const char* ecs_os_strerror(int err) { +#if defined(_MSC_VER) + strerror_s(error_str, 255, err); + return error_str; +#else + return strerror(err); +#endif +} diff --git a/fggl/ecs2/flecs/src/private_api.h b/fggl/ecs2/flecs/src/private_api.h new file mode 100644 index 0000000000000000000000000000000000000000..e613a6e4663dd09ded8412e1ff0cb3f1ce03ee6f --- /dev/null +++ b/fggl/ecs2/flecs/src/private_api.h @@ -0,0 +1,668 @@ +#ifndef FLECS_PRIVATE_H +#define FLECS_PRIVATE_H + +#include "private_types.h" + +//////////////////////////////////////////////////////////////////////////////// +//// Core bootstrap functions +//////////////////////////////////////////////////////////////////////////////// + +#define ECS_TYPE_DECL(component)\ +static const ecs_entity_t __##component = ecs_id(component);\ +ECS_VECTOR_DECL(FLECS__T##component, ecs_entity_t, 1) + +#define ECS_TYPE_IMPL(component)\ +ECS_VECTOR_IMPL(FLECS__T##component, ecs_entity_t, &__##component, 1) + +/* Bootstrap world */ +void flecs_bootstrap( + ecs_world_t *world); + +ecs_type_t flecs_bootstrap_type( + ecs_world_t *world, + ecs_entity_t entity); + +#define flecs_bootstrap_component(world, id)\ + ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = {\ + .entity = ecs_id(id),\ + .name = #id,\ + .symbol = #id\ + },\ + .size = sizeof(id),\ + .alignment = ECS_ALIGNOF(id)\ + }); + +#define flecs_bootstrap_tag(world, name)\ + ecs_set_name(world, name, (char*)&#name[ecs_os_strlen("Ecs")]);\ + ecs_set_symbol(world, name, #name);\ + ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world)) + + +/* 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_set_watch( + ecs_world_t *world, + ecs_entity_t entity); + +/* Obtain entity info */ +bool flecs_get_info( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_info_t *info); + +void flecs_run_monitors( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_vector_t *v_dst_monitors, + int32_t dst_row, + int32_t count, + ecs_vector_t *v_src_monitors); + +void flecs_register_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +void flecs_unregister_name( + ecs_world_t *world, + ecs_entity_t entity); + + +//////////////////////////////////////////////////////////////////////////////// +//// World API +//////////////////////////////////////////////////////////////////////////////// + +/* Get current stage */ +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr); + +/* Get current thread-specific stage from readonly world */ +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world); + +/* Get component callbacks */ +const ecs_type_info_t *flecs_get_c_info( + const ecs_world_t *world, + ecs_entity_t component); + +/* Get or create component callbacks */ +ecs_type_info_t * flecs_get_or_create_c_info( + 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 relation, + ecs_entity_t id); + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t relation, + 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_notify_queries( + ecs_world_t *world, + ecs_query_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); + +ecs_id_record_t* flecs_ensure_id_record( + const ecs_world_t *world, + ecs_id_t id); + +ecs_id_record_t* flecs_get_id_record( + const ecs_world_t *world, + ecs_id_t id); + +ecs_table_record_t* flecs_get_table_record( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +void flecs_clear_id_record( + const ecs_world_t *world, + ecs_id_t id); + +void flecs_triggers_notify( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count); + +ecs_map_t* flecs_triggers_get( + const ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event); + +void flecs_trigger_fini( + ecs_world_t *world, + ecs_trigger_t *trigger); + +void flecs_observer_fini( + ecs_world_t *world, + ecs_observer_t *observer); + +void flecs_use_intern( + ecs_entity_t entity, + const char *name, + ecs_vector_t **alias_vector); + + +//////////////////////////////////////////////////////////////////////////////// +//// Stage API +//////////////////////////////////////////////////////////////////////////////// + +/* Initialize stage data structures */ +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Deinitialize stage */ +void flecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Post-frame merge actions */ +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage); + +/* Delete table from stage */ +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table); + + +//////////////////////////////////////////////////////////////////////////////// +//// Defer API +//////////////////////////////////////////////////////////////////////////////// + +bool flecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component); + +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components); + +bool flecs_defer_clone( + ecs_world_t *world, + 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, + const ecs_ids_t *components, + void **component_data, + const ecs_entity_t **ids_out); + +bool flecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity); + +bool flecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components); + +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components); + +bool flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added); + +bool flecs_defer_flush( + ecs_world_t *world, + ecs_stage_t *stage); + +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage); + +//////////////////////////////////////////////////////////////////////////////// +//// Type API +//////////////////////////////////////////////////////////////////////////////// + +/* Test if type_id_1 contains type_id_2 */ +ecs_entity_t flecs_type_contains( + const ecs_world_t *world, + ecs_type_t type_id_1, + ecs_type_t type_id_2, + bool match_all, + bool match_prefab); + +void flecs_run_add_actions( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_ids_t *added, + bool get_all, + bool run_on_set); + +void flecs_run_remove_actions( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count, + ecs_ids_t *removed); + +void flecs_run_set_systems( + ecs_world_t *world, + ecs_id_t component, + ecs_table_t *table, + ecs_data_t *data, + ecs_column_t *column, + int32_t row, + int32_t count, + bool set_all); + + +//////////////////////////////////////////////////////////////////////////////// +//// Table API +//////////////////////////////////////////////////////////////////////////////// + +/** Find or create table for a set of components */ +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + const ecs_ids_t *type); + +/* Get table data */ +ecs_data_t *flecs_table_get_data( + const ecs_table_t *table); + +/* Get or create data */ +ecs_data_t *flecs_table_get_or_create_data( + ecs_table_t *table); + +/* Initialize columns for data */ +ecs_data_t* flecs_init_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *result); + +/* 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); + +/* Clear table data. Don't call OnRemove handlers. */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* 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_data_t *data, + ecs_entity_t entity, + ecs_record_t *record, + bool construct); + +/* Delete an entity from the table. */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t index, + bool destruct); + +/* Move a row from one table to another */ +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + bool construct); + +/* 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); + +/* Match table with filter */ +bool flecs_table_match_filter( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_filter_t *filter); + +bool flecs_filter_match_table( + ecs_world_t *world, + const ecs_filter_t *filter, + const ecs_table_t *table, + ecs_type_t type, + ecs_id_t *ids, + int32_t *columns, + ecs_type_t *types, + ecs_entity_t *subjects, + ecs_size_t *sizes, + void **ptrs); + +/* Get dirty state for table columns */ +int32_t* flecs_table_get_dirty_state( + ecs_table_t *table); + +/* Get monitor for monitoring table changes */ +int32_t* flecs_table_get_monitor( + 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_free( + ecs_world_t *world, + ecs_table_t *table); + +/* Free table */ +void flecs_table_free_type( + ecs_table_t *table); + +/* Replace data */ +void flecs_table_replace_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data); + +/* Merge data of one table into another table */ +ecs_data_t* flecs_table_merge( + ecs_world_t *world, + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data); + +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2); + +ecs_table_t *flecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *to_add, + ecs_ids_t *added); + +ecs_table_t *flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_ids_t *to_remove, + ecs_ids_t *removed); + +void flecs_table_mark_dirty( + ecs_table_t *table, + ecs_entity_t component); + +const EcsComponent* flecs_component_from_id( + const ecs_world_t *world, + ecs_entity_t e); + +int32_t flecs_table_switch_from_case( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t add); + +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event); + +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table); + +ecs_column_t *ecs_table_column_for_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id); + +//////////////////////////////////////////////////////////////////////////////// +//// Query API +//////////////////////////////////////////////////////////////////////////////// + +void flecs_query_set_iter( + ecs_world_t *world, + ecs_query_t *query, + ecs_iter_t *it, + int32_t table_index, + int32_t row, + int32_t count); + +void flecs_run_monitor( + ecs_world_t *world, + ecs_matched_query_t *monitor, + ecs_ids_t *components, + int32_t row, + int32_t count, + ecs_entity_t *entities); + +bool flecs_query_match( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_query_t *query, + ecs_match_failure_t *failure_info); + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event); + +void ecs_iter_init( + ecs_iter_t *it); + +void ecs_iter_fini( + ecs_iter_t *it); + +//////////////////////////////////////////////////////////////////////////////// +//// Time API +//////////////////////////////////////////////////////////////////////////////// + +void flecs_os_time_setup(void); + +uint64_t flecs_os_time_now(void); + +void flecs_os_time_sleep( + int32_t sec, + int32_t nanosec); + +/* Increase or reset timer resolution (Windows only) */ +FLECS_API +void flecs_increase_timer_resolution( + bool enable); + +//////////////////////////////////////////////////////////////////////////////// +//// Utilities +//////////////////////////////////////////////////////////////////////////////// + +uint64_t flecs_hash( + const void *data, + ecs_size_t length); + +/* Convert 64 bit signed integer to 16 bit */ +int8_t flflecs_to_i8( + int64_t v); + +/* Convert 64 bit signed integer to 16 bit */ +int16_t flecs_to_i16( + int64_t v); + +/* Convert 64 bit unsigned integer to 32 bit */ +uint32_t flecs_to_u32( + uint64_t v); + +/* Convert signed integer to size_t */ +size_t flecs_to_size_t( + int64_t size); + +/* Convert size_t to ecs_size_t */ +ecs_size_t flecs_from_size_t( + size_t size); + +/* 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); + +/* Get actual row from record row */ +int32_t flecs_record_to_row( + int32_t row, + bool *is_watched_out); + +/* Convert actual row to record row */ +int32_t flecs_row_to_record( + int32_t row, + bool is_watched); + +/* Convert type to entity array */ +ecs_ids_t flecs_type_to_ids( + ecs_type_t type); + +/* 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 */ +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); + +/* Compare function for entity ids which can be used with qsort */ +int flecs_entity_compare_qsort( + const void *e1, + const void *e2); + +uint64_t flecs_string_hash( + const void *ptr); + +ecs_hashmap_t flecs_table_hashmap_new(void); +ecs_hashmap_t flecs_string_hashmap_new(void); + +#define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) +void _assert_func( + bool cond, + const char *cond_str, + const char *file, + int32_t line, + const char *func); + +#endif diff --git a/fggl/ecs2/flecs/src/private_types.h b/fggl/ecs2/flecs/src/private_types.h new file mode 100644 index 0000000000000000000000000000000000000000..b55626b819a557c2f940b3d5afe187a97a00d34f --- /dev/null +++ b/fggl/ecs2/flecs/src/private_types.h @@ -0,0 +1,631 @@ +#ifndef FLECS_TYPES_PRIVATE_H +#define FLECS_TYPES_PRIVATE_H + +#ifndef __MACH__ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <time.h> +#include <ctype.h> +#include <math.h> + +#ifdef _MSC_VER +//FIXME +#else +#include <sys/param.h> /* attempt to define endianness */ +#endif +#ifdef linux +# include <endian.h> /* attempt to define endianness */ +#endif + +#include "flecs.h" +#include "entity_index.h" +#include "flecs/private/bitset.h" +#include "flecs/private/sparse.h" +#include "flecs/private/switch_list.h" +#include "flecs/private/hashmap.h" +#include "flecs/type.h" + +#define ECS_MAX_JOBS_PER_WORKER (16) + +/** These values are used to verify validity of the pointers passed into the API + * and to allow for passing a thread as a world to some API calls (this allows + * for transparently passing thread context to API functions) */ +#define ECS_WORLD_MAGIC (0x65637377) +#define ECS_STAGE_MAGIC (0x65637374) + +/* Maximum number of entities that can be added in a single operation. + * Increasing this value will increase consumption of stack space. */ +#define ECS_MAX_ADD_REMOVE (32) + +/** Type used for internal string hashmap */ +typedef struct ecs_string_t { + char *value; + ecs_size_t length; + uint64_t hash; +} ecs_string_t; + +/** Component-specific data */ +typedef struct ecs_type_info_t { + EcsComponentLifecycle lifecycle; /* Component lifecycle callbacks */ + ecs_entity_t component; + ecs_size_t size; + ecs_size_t alignment; + bool lifecycle_set; +} ecs_type_info_t; + +/* Table event type for notifying tables of world events */ +typedef enum ecs_table_eventkind_t { + EcsTableQueryMatch, + EcsTableQueryUnmatch, + EcsTableTriggerMatch, + EcsTableComponentInfo +} ecs_table_eventkind_t; + +typedef struct ecs_table_event_t { + ecs_table_eventkind_t kind; + + /* Query event */ + ecs_query_t *query; + int32_t matched_table_index; + + /* Component info event */ + ecs_entity_t component; + + /* Trigger match */ + ecs_entity_t event; + + /* If the nubmer of fields gets out of hand, this can be turned into a union + * but since events are very temporary objects, this works for now and makes + * initializing an event a bit simpler. */ +} ecs_table_event_t; + +/** A component column. */ +struct ecs_column_t { + ecs_vector_t *data; /**< Column data */ + int16_t size; /**< Column element size */ + int16_t alignment; /**< Column element alignment */ +}; + +/** A switch column. */ +typedef struct ecs_sw_column_t { + ecs_switch_t *data; /**< Column data */ + ecs_type_t type; /**< Switch type */ +} ecs_sw_column_t; + +/** A bitset column. */ +typedef struct ecs_bs_column_t { + ecs_bitset_t data; /**< Column data */ +} ecs_bs_column_t; + +/** Stage-specific component data */ +struct ecs_data_t { + ecs_vector_t *entities; /**< Entity identifiers */ + ecs_vector_t *record_ptrs; /**< Ptrs to records in main entity index */ + ecs_column_t *columns; /**< Component columns */ + ecs_sw_column_t *sw_columns; /**< Switch columns */ + ecs_bs_column_t *bs_columns; /**< Bitset columns */ +}; + +/** Small footprint data structure for storing data associated with a table. */ +typedef struct ecs_table_leaf_t { + ecs_table_t *table; + ecs_type_t type; + ecs_data_t *data; +} ecs_table_leaf_t; + +/** Flags for quickly checking for special properties of a table. */ +#define EcsTableHasBuiltins 1u /**< Does table have builtin components */ +#define EcsTableIsPrefab 2u /**< Does the table store prefabs */ +#define EcsTableHasIsA 4u /**< Does the table type has IsA */ +#define EcsTableHasModule 8u /**< Does the table have module data */ +#define EcsTableHasXor 32u /**< Does the table type has XOR */ +#define EcsTableIsDisabled 64u /**< Does the table type has EcsDisabled */ +#define EcsTableHasCtors 128u +#define EcsTableHasDtors 256u +#define EcsTableHasCopy 512u +#define EcsTableHasMove 1024u +#define EcsTableHasOnAdd 2048u +#define EcsTableHasOnRemove 4096u +#define EcsTableHasOnSet 8192u +#define EcsTableHasUnSet 16384u +#define EcsTableHasMonitors 32768u +#define EcsTableHasSwitch 65536u +#define EcsTableHasDisabled 131072u + +/* Composite constants */ +#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) +#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasSwitch | EcsTableHasDisabled) +#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasSwitch | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet | EcsTableHasMonitors) +#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet | EcsTableHasMonitors) + +/** Edge used for traversing the table graph. */ +typedef struct ecs_edge_t { + ecs_table_t *add; /**< Edges traversed when adding */ + ecs_table_t *remove; /**< Edges traversed when removing */ +} ecs_edge_t; + +/** Quey matched with table with backref to query table administration. + * This type is used to store a matched query together with the array index of + * where the table is stored in the query administration. This type is used when + * an action that originates on a table needs to invoke a query (system) and a + * fast lookup is required for the query administration, as is the case with + * OnSet and Monitor systems. */ +typedef struct ecs_matched_query_t { + ecs_query_t *query; /**< The query matched with the table */ + int32_t matched_table_index; /**< Table index in the query type */ +} ecs_matched_query_t; + +/** A table is the Flecs equivalent of an archetype. Tables store all entities + * with a specific set of components. Tables are automatically created when an + * entity has a set of components not previously observed before. When a new + * table is created, it is automatically matched with existing queries */ +struct ecs_table_t { + uint64_t id; /**< Table id in sparse set */ + ecs_type_t type; /**< Identifies table type in type_index */ + ecs_flags32_t flags; /**< Flags for testing table properties */ + int32_t column_count; /**< Number of data columns in table */ + + ecs_data_t *data; /**< Component storage */ + ecs_type_info_t **c_info; /**< Cached pointers to component info */ + + ecs_edge_t *lo_edges; /**< Edges to other tables */ + ecs_map_t *hi_edges; + + ecs_vector_t *queries; /**< Queries matched with table */ + ecs_vector_t *monitors; /**< Monitor systems matched with table */ + ecs_vector_t **on_set; /**< OnSet systems, broken up by column */ + ecs_vector_t *on_set_all; /**< All OnSet systems */ + ecs_vector_t *on_set_override; /**< All OnSet systems with overrides */ + ecs_vector_t *un_set_all; /**< All UnSet systems */ + + int32_t *dirty_state; /**< Keep track of changes in columns */ + int32_t alloc_count; /**< Increases when columns are reallocd */ + + int32_t sw_column_count; + int32_t sw_column_offset; + int32_t bs_column_count; + int32_t bs_column_offset; + + int32_t lock; +}; + +/* Sparse query column */ +typedef struct flecs_sparse_column_t { + ecs_sw_column_t *sw_column; + ecs_entity_t sw_case; + int32_t signature_column_index; +} flecs_sparse_column_t; + +/* Bitset query column */ +typedef struct flecs_bitset_column_t { + ecs_bs_column_t *bs_column; + int32_t column_index; +} flecs_bitset_column_t; + +/** Type containing data for a table matched with a query. */ +typedef struct ecs_matched_table_t { + int32_t *columns; /**< Mapping from query terms to table columns */ + ecs_table_t *table; /**< The current table. */ + ecs_data_t *data; /**< Table component data */ + ecs_id_t *ids; /**< Resolved (component) ids for current table */ + ecs_entity_t *subjects; /**< Subjects (sources) of ids */ + ecs_type_t *types; /**< Types for ids for current table */ + ecs_size_t *sizes; /**< Sizes for ids for current table */ + ecs_ref_t *references; /**< Cached components for non-this terms */ + + ecs_vector_t *sparse_columns; /**< Column ids of sparse columns */ + ecs_vector_t *bitset_columns; /**< Column ids with disabled flags */ + int32_t *monitor; /**< Used to monitor table for changes */ + int32_t rank; /**< Rank used to sort tables */ +} ecs_matched_table_t; + +/** Type used to track location of table in queries' table lists. + * When a table becomes empty or non-empty a signal is sent to a query, which + * moves the table to or from an empty list. While this ensures that when + * iterating no time is spent on iterating over empty tables, doing a linear + * search for the table in either list can take a significant amount of time if + * a query is matched with many tables. + * + * To avoid a linear search, the query has a map with table indices that can + * return the location of the table in either list in constant time. + * + * If a table is matched multiple times by a query, such as can happen when a + * query matches pairs, a table can occupy multiple indices. + */ +typedef struct ecs_table_indices_t { + int32_t *indices; /* If indices are negative, table is in empty list */ + int32_t count; +} ecs_table_indices_t; + +/** Type storing an entity range within a table. + * This type is used for iterating in orer across archetypes. A sorting function + * constructs a list of the ranges across archetypes that are in order so that + * when the query iterates over the archetypes, it only needs to iterate the + * list of ranges. */ +typedef struct ecs_table_slice_t { + ecs_matched_table_t *table; /**< Reference to the matched table */ + int32_t start_row; /**< Start of range */ + int32_t count; /**< Number of entities in range */ +} ecs_table_slice_t; + +#define EcsQueryNeedsTables (1) /* Query needs matching with tables */ +#define EcsQueryMonitor (2) /* Query needs to be registered as a monitor */ +#define EcsQueryOnSet (4) /* Query needs to be registered as on_set system */ +#define EcsQueryUnSet (8) /* Query needs to be registered as un_set system */ +#define EcsQueryMatchDisabled (16) /* Does query match disabled */ +#define EcsQueryMatchPrefab (32) /* Does query match prefabs */ +#define EcsQueryHasRefs (64) /* Does query have references */ +#define EcsQueryHasTraits (128) /* Does query have pairs */ +#define EcsQueryIsSubquery (256) /* Is query a subquery */ +#define EcsQueryIsOrphaned (512) /* Is subquery orphaned */ +#define EcsQueryHasOutColumns (1024) /* Does query have out columns */ +#define EcsQueryHasOptional (2048) /* Does query have optional columns */ + +#define EcsQueryNoActivation (EcsQueryMonitor | EcsQueryOnSet | EcsQueryUnSet) + +/* Query event type for notifying queries of world events */ +typedef enum ecs_query_eventkind_t { + EcsQueryTableMatch, + EcsQueryTableEmpty, + EcsQueryTableNonEmpty, + EcsQueryTableRematch, + EcsQueryTableUnmatch, + EcsQueryOrphan +} ecs_query_eventkind_t; + +typedef struct ecs_query_event_t { + ecs_query_eventkind_t kind; + ecs_table_t *table; + ecs_query_t *parent_query; +} ecs_query_event_t; + +/** Query that is automatically matched against active tables */ +struct ecs_query_t { + /* Signature of query */ + ecs_filter_t filter; + + /* Reference to world */ + ecs_world_t *world; + + /* Tables matched with query */ + ecs_vector_t *tables; + ecs_vector_t *empty_tables; + ecs_map_t *table_indices; + + /* Handle to system (optional) */ + ecs_entity_t system; + + /* Used for sorting */ + ecs_entity_t order_by_component; + ecs_order_by_action_t order_by; + ecs_vector_t *table_slices; + + /* Used for table sorting */ + ecs_entity_t group_by_id; + ecs_group_by_action_t group_by; + void *group_by_ctx; + ecs_ctx_free_t group_by_ctx_free; + + /* Subqueries */ + ecs_query_t *parent; + ecs_vector_t *subqueries; + + /* The query kind determines how it is registered with tables */ + ecs_flags32_t flags; + + uint64_t id; /* Id of query in query storage */ + int32_t cascade_by; /* Identify CASCADE column */ + int32_t match_count; /* How often have tables been (un)matched */ + int32_t prev_match_count; /* Used to track if sorting is needed */ + + bool needs_reorder; /* Whether next iteration should reorder */ + bool constraints_satisfied; /* Are all term constraints satisfied */ +}; + +/** Event mask */ +#define EcsEventAdd (1) +#define EcsEventRemove (2) + +/** Triggers for a specific id */ +typedef struct ecs_id_trigger_t { + ecs_map_t *on_add_triggers; + ecs_map_t *on_remove_triggers; + ecs_map_t *on_set_triggers; + ecs_map_t *un_set_triggers; +} ecs_id_trigger_t; + +/** Keep track of how many [in] columns are active for [out] columns of OnDemand + * systems. */ +typedef struct ecs_on_demand_out_t { + ecs_entity_t system; /* Handle to system */ + int32_t count; /* Total number of times [out] columns are used */ +} ecs_on_demand_out_t; + +/** Keep track of which OnDemand systems are matched with which [in] columns */ +typedef struct ecs_on_demand_in_t { + int32_t count; /* Number of active systems with [in] column */ + ecs_vector_t *systems; /* Systems that have this column as [out] column */ +} ecs_on_demand_in_t; + +/** Types for deferred operations */ +typedef enum ecs_op_kind_t { + EcsOpNew, + EcsOpClone, + EcsOpBulkNew, + EcsOpAdd, + EcsOpRemove, + EcsOpSet, + EcsOpMut, + EcsOpModified, + EcsOpDelete, + EcsOpClear, + EcsOpEnable, + EcsOpDisable +} ecs_op_kind_t; + +typedef struct ecs_op_1_t { + ecs_entity_t entity; /* Entity id */ + void *value; /* Value (used for set / get_mut) */ + ecs_size_t size; /* Size of value */ + bool clone_value; /* Clone entity with value (used for clone) */ +} ecs_op_1_t; + +typedef struct ecs_op_n_t { + ecs_entity_t *entities; + void **bulk_data; + int32_t count; +} ecs_op_n_t; + +typedef struct ecs_op_t { + ecs_op_kind_t kind; /* Operation kind */ + ecs_entity_t component; /* Single component (components.count = 1) */ + ecs_ids_t components; /* Multiple components */ + union { + ecs_op_1_t _1; + ecs_op_n_t _n; + } is; +} ecs_op_t; + +/** A stage is a data structure in which delta's are stored until it is safe to + * merge those delta's with the main world stage. A stage allows flecs systems + * to arbitrarily add/remove/set components and create/delete entities while + * iterating. Additionally, worker threads have their own stage that lets them + * mutate the state of entities without requiring locks. */ +struct ecs_stage_t { + int32_t magic; /* Magic number to verify thread pointer */ + int32_t id; /* Unique id that identifies the stage */ + + /* Are operations deferred? */ + int32_t defer; + ecs_vector_t *defer_queue; + + 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_vector_t *post_frame_actions; + + /* Namespacing */ + ecs_table_t *scope_table; /* Table for current scope */ + ecs_entity_t scope; /* Entity of current scope */ + ecs_entity_t with; /* Id to add by default to new entities */ + + /* Properties */ + bool auto_merge; /* Should this stage automatically merge? */ + bool asynchronous; /* Is stage asynchronous? (write only) */ +}; + +/* Component monitor */ +typedef struct ecs_monitor_t { + ecs_vector_t *queries; /* vector<ecs_query_t*> */ + bool is_dirty; /* Should queries be rematched? */ +} ecs_monitor_t; + +/* Component monitors */ +typedef struct ecs_monitor_set_t { + ecs_map_t *monitors; /* map<id, ecs_monitor_t> */ + bool is_dirty; /* Should monitors be evaluated? */ +} ecs_monitor_set_t; + +/* Relation monitors. TODO: implement generic monitor mechanism */ +typedef struct ecs_relation_monitor_t { + ecs_map_t *monitor_sets; /* map<relation_id, ecs_monitor_set_t> */ + bool is_dirty; /* Should monitor sets be evaluated? */ +} ecs_relation_monitor_t; + +/* Payload for table index which returns all tables for a given component, with + * the column of the component in the table. */ +typedef struct ecs_table_record_t { + ecs_table_t *table; + int32_t column; + int32_t count; +} ecs_table_record_t; + +/* Payload for id index which contains all datastructures for an id. */ +struct ecs_id_record_t { + /* All tables that contain the id */ + ecs_map_t *table_index; /* map<table_id, ecs_table_record_t> */ + + ecs_entity_t on_delete; /* Cleanup action for removing id */ + ecs_entity_t on_delete_object; /* Cleanup action for removing object */ +}; + +typedef struct ecs_store_t { + /* Entity lookup */ + ecs_sparse_t *entity_index; /* sparse<entity, ecs_record_t> */ + + /* Table lookup by id */ + ecs_sparse_t *tables; /* sparse<table_id, ecs_table_t> */ + + /* Table lookup by hash */ + ecs_hashmap_t table_map; /* hashmap<ecs_ids_t, ecs_table_t*> */ + + /* Root table */ + ecs_table_t root; +} ecs_store_t; + +/** Supporting type to store looked up or derived entity data */ +typedef struct ecs_entity_info_t { + ecs_record_t *record; /* Main stage record in entity index */ + ecs_table_t *table; /* Table. Not set if entity is empty */ + ecs_data_t *data; /* Stage-specific table columns */ + int32_t row; /* Actual row (stripped from is_watched bit) */ + bool is_watched; /* Is entity being watched */ +} ecs_entity_info_t; + +/** Supporting type to store looked up component data in specific table */ +typedef struct ecs_column_info_t { + ecs_entity_t id; + const ecs_type_info_t *ci; + int32_t column; +} ecs_column_info_t; + +/* fini actions */ +typedef struct ecs_action_elem_t { + ecs_fini_action_t action; + void *ctx; +} ecs_action_elem_t; + +/* Alias */ +typedef struct ecs_alias_t { + char *name; + ecs_entity_t entity; +} ecs_alias_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 { + int32_t magic; /* Magic number to verify world pointer */ + + /* -- Type metadata -- */ + + ecs_map_t *id_index; /* map<id, ecs_id_record_t> */ + ecs_map_t *id_triggers; /* map<id, ecs_id_trigger_t> */ + ecs_sparse_t *type_info; /* sparse<type_id, type_info_t> */ + + /* Is entity range checking enabled? */ + bool range_check_enabled; + + + /* -- Data storage -- */ + + ecs_store_t store; + + + /* -- Storages for API objects -- */ + + ecs_sparse_t *queries; /* sparse<query_id, ecs_query_t> */ + ecs_sparse_t *triggers; /* sparse<query_id, ecs_trigger_t> */ + ecs_sparse_t *observers; /* sparse<query_id, ecs_observer_t> */ + + + /* Keep track of components that were added/removed to/from monitored + * entities. Monitored entities are entities that a query has matched with + * specifically, as is the case with PARENT / CASCADE columns, FromEntity + * columns and columns matched from prefabs. + * When these entities change type, queries may have to be rematched. + * Queries register themselves as component monitors for specific components + * and when these components change they are rematched. The component + * monitors are evaluated during a merge. */ + ecs_relation_monitor_t monitors; + + /* -- Systems -- */ + + ecs_entity_t pipeline; /* Current pipeline */ + ecs_map_t *on_activate_components; /* Trigger on activate of [in] column */ + ecs_map_t *on_enable_components; /* Trigger on enable of [in] column */ + ecs_vector_t *fini_tasks; /* Tasks to execute on ecs_fini */ + + + /* -- Lookup Indices -- */ + + ecs_map_t *type_handles; /* Handles to named types */ + + + /* -- Aliasses -- */ + + ecs_hashmap_t aliases; + ecs_hashmap_t symbols; + + + /* -- Staging -- */ + + ecs_stage_t stage; /* Main storage */ + ecs_vector_t *worker_stages; /* Stages for threads */ + + + /* -- Hierarchy administration -- */ + + const char *name_prefix; /* Remove prefix from C names in modules */ + + + /* -- 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 */ + + + /* -- Time management -- */ + + ecs_time_t world_start_time; /* Timestamp of simulation start */ + ecs_time_t frame_start_time; /* Timestamp of frame start */ + FLECS_FLOAT fps_sleep; /* Sleep time to prevent fps overshoot */ + + + /* -- Metrics -- */ + + ecs_world_info_t stats; + + + /* -- Settings from command line arguments -- */ + + int arg_fps; + int arg_threads; + + + /* -- World lock -- */ + + ecs_os_mutex_t mutex; /* Locks the world if locking enabled */ + ecs_os_mutex_t thr_sync; /* Used to signal threads at end of frame */ + ecs_os_cond_t thr_cond; /* Used to signal threads at end of frame */ + + + /* -- Defered operation count -- */ + + int32_t new_count; + int32_t bulk_new_count; + int32_t delete_count; + int32_t clear_count; + int32_t add_count; + int32_t remove_count; + int32_t set_count; + int32_t discard_count; + + + /* -- World state -- */ + + bool quit_workers; /* Signals worker threads to quit */ + bool is_readonly; /* Is world being progressed */ + bool is_fini; /* Is the world being cleaned up? */ + bool measure_frame_time; /* Time spent on each frame */ + bool measure_system_time; /* Time spent by each system */ + bool should_quit; /* Did a system signal that app should quit */ + bool locking_enabled; /* Lock world when in progress */ + + void *context; /* Application context */ + ecs_vector_t *fini_actions; /* Callbacks to execute when world exits */ +}; + +#endif diff --git a/fggl/ecs2/flecs/src/query.c b/fggl/ecs2/flecs/src/query.c new file mode 100644 index 0000000000000000000000000000000000000000..bb27f9587143380714deeba39882a3b18d4e3644 --- /dev/null +++ b/fggl/ecs2/flecs/src/query.c @@ -0,0 +1,3084 @@ +#include "private_api.h" + +#ifdef FLECS_SYSTEMS_H +#include "modules/system/system.h" +#endif + +static +void activate_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table, + bool active); + +/* Builtin group_by callback for Cascade terms. + * This function traces the hierarchy depth of an entity type by following a + * relation upwards (to its 'parents') for as long as those parents have the + * specified component id. + * The result of the function is the number of parents with the provided + * component for a given relation. */ +static +int32_t group_by_cascade( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component, + void *ctx) +{ + int32_t result = 0; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + ecs_term_t *term = ctx; + ecs_entity_t relation = term->args[0].set.relation; + + /* Cascade needs a relation to calculate depth from */ + ecs_assert(relation != 0, ECS_INVALID_PARAMETER, NULL); + + /* Should only be used with cascade terms */ + ecs_assert(term->args[0].set.mask & EcsCascade, + ECS_INVALID_PARAMETER, NULL); + + /* Iterate back to front as relations are more likely to occur near the + * end of a type. */ + for (i = count - 1; i >= 0; i --) { + /* Find relation & relation object in entity type */ + if (ECS_HAS_RELATION(array[i], relation)) { + ecs_type_t obj_type = ecs_get_type(world, + ecs_pair_object(world, array[i])); + int32_t j, c_count = ecs_vector_count(obj_type); + ecs_entity_t *c_array = ecs_vector_first(obj_type, ecs_entity_t); + + /* Iterate object type, check if it has the specified component */ + for (j = 0; j < c_count; j ++) { + /* If it has the component, it is part of the tree matched by + * the query, increase depth */ + if (c_array[j] == component) { + result ++; + + /* Recurse to test if the object has matching parents */ + result += group_by_cascade(world, obj_type, component, ctx); + break; + } + } + + if (j != c_count) { + break; + } + + /* If the id doesn't have a role set, we'll find no more relations */ + } else if (!(array[i] & ECS_ROLE_MASK)) { + break; + } + } + + return result; +} + +static +int table_compare( + const void *t1, + const void *t2) +{ + const ecs_matched_table_t *table_1 = t1; + const ecs_matched_table_t *table_2 = t2; + + return table_1->rank - table_2->rank; +} + +static +bool has_auto_activation( + ecs_query_t *q) +{ + /* Only a basic query with no additional features does table activation */ + return !(q->flags & EcsQueryNoActivation); +} + +static +void order_grouped_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + if (query->group_by) { + ecs_vector_sort(query->tables, ecs_matched_table_t, table_compare); + + /* Recompute the table indices by first resetting all indices, and then + * re-adding them one by one. */ + if (has_auto_activation(query)) { + ecs_map_iter_t it = ecs_map_iter(query->table_indices); + ecs_table_indices_t *ti; + while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) { + /* If table is registered, it must have at least one index */ + int32_t count = ti->count; + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + (void)count; + + /* Only active tables are reordered, so don't reset inactive + * tables */ + if (ti->indices[0] >= 0) { + ti->count = 0; + } + } + } + + /* Re-register monitors after tables have been reordered. This will update + * the table administration with the new matched_table ids, so that when a + * monitor is executed we can quickly find the right matched_table. */ + if (query->flags & EcsQueryMonitor) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + flecs_table_notify(world, table->table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query, + .matched_table_index = table_i + }); + }); + } + + /* Update table index */ + if (has_auto_activation(query)) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + ecs_table_indices_t *ti = ecs_map_get(query->table_indices, + ecs_table_indices_t, table->table->id); + + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ti->indices[ti->count] = table_i; + ti->count ++; + }); + } + } + + query->match_count ++; + query->needs_reorder = false; +} + +static +void group_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_matched_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (query->group_by) { + ecs_assert(table->table != NULL, ECS_INTERNAL_ERROR, NULL); + table->rank = query->group_by(world, table->table->type, + query->group_by_id, query->group_by_ctx); + } else { + table->rank = 0; + } +} + +/* Rank all tables of query. Only necessary if a new ranking function was + * provided or if a monitored entity set the component used for ranking. */ +static +void group_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + if (query->group_by) { + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + group_table(world, query, table); + }); + + ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, { + group_table(world, query, table); + }); + } +} + +#ifndef NDEBUG + +static +const char* query_name( + ecs_world_t *world, + ecs_query_t *q) +{ + if (q->system) { + return ecs_get_name(world, q->system); + } else if (q->filter.name) { + return q->filter.name; + } else { + return q->filter.expr; + } +} + +#endif + +static +int get_comp_and_src( + ecs_world_t *world, + ecs_query_t *query, + int32_t t, + ecs_table_t *table_arg, + ecs_entity_t *component_out, + ecs_entity_t *entity_out) +{ + ecs_entity_t component = 0, entity = 0; + + ecs_term_t *terms = query->filter.terms; + int32_t term_count = query->filter.term_count; + ecs_term_t *term = &terms[t]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t op = term->oper; + + if (op == EcsNot) { + entity = subj->entity; + } + + if (!subj->entity) { + component = term->id; + } else { + ecs_table_t *table = table_arg; + if (subj->entity != EcsThis) { + table = ecs_get_table(world, subj->entity); + } + + ecs_type_t type = NULL; + if (table) { + type = table->type; + } + + if (op == EcsOr) { + for (; t < term_count; t ++) { + term = &terms[t]; + + /* Keep iterating until the next non-OR expression */ + if (term->oper != EcsOr) { + t --; + break; + } + + if (!component) { + ecs_entity_t source = 0; + int32_t result = ecs_type_match(world, table, type, + 0, term->id, subj->set.relation, subj->set.min_depth, + subj->set.max_depth, + &source); + + if (result != -1) { + component = term->id; + } + + if (source) { + entity = source; + } + } + } + } else { + component = term->id; + + ecs_entity_t source = 0; + bool result = ecs_type_match(world, table, type, 0, component, + subj->set.relation, subj->set.min_depth, subj->set.max_depth, + &source) != -1; + + if (op == EcsNot) { + result = !result; + } + + /* Optional terms may not have the component. *From terms contain + * the id of a type of which the contents must match, but the type + * itself does not need to match. */ + if (op == EcsOptional || op == EcsAndFrom || op == EcsOrFrom || + op == EcsNotFrom) + { + result = true; + } + + /* Table has already been matched, so unless column is optional + * any components matched from the table must be available. */ + if (table == table_arg) { + ecs_assert(result == true, ECS_INTERNAL_ERROR, NULL); + } + + if (source) { + entity = source; + } + } + + if (subj->entity != EcsThis) { + entity = subj->entity; + } + } + + if (entity == EcsThis) { + entity = 0; + } + + *component_out = component; + *entity_out = entity; + + return t; +} + +typedef struct pair_offset_t { + int32_t index; + int32_t count; +} pair_offset_t; + +/* Get index for specified pair. Take into account that a pair can be matched + * multiple times per table, by keeping an offset of the last found index */ +static +int32_t get_pair_index( + ecs_type_t table_type, + ecs_id_t pair, + int32_t column_index, + pair_offset_t *pair_offsets, + int32_t count) +{ + int32_t result; + + /* The count variable keeps track of the number of times a pair has been + * matched with the current table. Compare the count to check if the index + * was already resolved for this iteration */ + if (pair_offsets[column_index].count == count) { + /* If it was resolved, return the last stored index. Subtract one as the + * index is offset by one, to ensure we're not getting stuck on the same + * index. */ + result = pair_offsets[column_index].index - 1; + } else { + /* First time for this iteration that the pair index is resolved, look + * it up in the type. */ + result = ecs_type_index_of(table_type, + pair_offsets[column_index].index, pair); + pair_offsets[column_index].index = result + 1; + pair_offsets[column_index].count = count; + } + + return result; +} + +static +int32_t get_component_index( + ecs_world_t *world, + ecs_table_t *table, + ecs_type_t table_type, + ecs_entity_t *component_out, + int32_t column_index, + ecs_oper_kind_t op, + pair_offset_t *pair_offsets, + int32_t count) +{ + int32_t result = 0; + ecs_entity_t component = *component_out; + + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (component) { + /* If requested component is a case, find the corresponding switch to + * lookup in the table */ + if (ECS_HAS_ROLE(component, CASE)) { + result = flecs_table_switch_from_case( + world, table, component); + ecs_assert(result != -1, ECS_INTERNAL_ERROR, NULL); + + result += table->sw_column_offset; + } else + if (ECS_HAS_ROLE(component, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(component); + ecs_entity_t obj = ECS_PAIR_OBJECT(component); + + /* Both the relationship and the object of the pair must be set */ + ecs_assert(rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(obj != 0, ECS_INVALID_PARAMETER, NULL); + + if (rel == EcsWildcard || obj == EcsWildcard) { + ecs_assert(pair_offsets != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get index of pair. Start looking from the last pair index + * as this may not be the first instance of the pair. */ + result = get_pair_index( + table_type, component, column_index, pair_offsets, count); + + if (result != -1) { + /* If component of current column is a pair, get the actual + * pair type for the table, so the system can see which + * component the pair was applied to */ + ecs_entity_t *pair = ecs_vector_get( + table_type, ecs_entity_t, result); + *component_out = *pair; + + char buf[256]; ecs_id_str(world, *pair, buf, 256); + + /* Check if the pair is a tag or whether it has data */ + if (ecs_get(world, rel, EcsComponent) == NULL) { + /* If pair has no data associated with it, use the + * component to which the pair has been added */ + component = ECS_PAIR_OBJECT(*pair); + } else { + component = rel; + } + } + } else { + + /* If the low part is a regular entity (component), then + * this query exactly matches a single pair instance. In + * this case we can simply do a lookup of the pair + * identifier in the table type. */ + result = ecs_type_index_of(table_type, 0, component); + } + } else { + /* Get column index for component */ + result = ecs_type_index_of(table_type, 0, component); + } + + /* If column is found, add one to the index, as column zero in + * a table is reserved for entity id's */ + if (result != -1) { + result ++; + } + + /* ecs_table_column_offset may return -1 if the component comes + * from a prefab. If so, the component will be resolved as a + * reference (see below) */ + } + + if (op == EcsAndFrom || op == EcsOrFrom || op == EcsNotFrom) { + result = 0; + } else if (op == EcsOptional) { + /* If table doesn't have the field, mark it as no data */ + if (!ecs_type_has_id(world, table_type, component, false)) { + result = 0; + } + } + + return result; +} + +static +ecs_vector_t* add_ref( + ecs_world_t *world, + ecs_query_t *query, + ecs_vector_t *references, + ecs_term_t *term, + ecs_entity_t component, + ecs_entity_t entity) +{ + ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); + ecs_term_id_t *subj = &term->args[0]; + + if (!(subj->set.mask & EcsCascade)) { + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + } + + *ref = (ecs_ref_t){0}; + ref->entity = entity; + ref->component = component; + + const EcsComponent *c_info = flecs_component_from_id(world, component); + if (c_info) { + if (c_info->size && subj->entity != 0) { + if (entity) { + ecs_get_ref_w_id(world, ref, entity, component); + } + + query->flags |= EcsQueryHasRefs; + } + } + + return references; +} + +static +int32_t get_pair_count( + ecs_type_t type, + ecs_entity_t pair) +{ + int32_t i = -1, result = 0; + while (-1 != (i = ecs_type_index_of(type, i + 1, pair))) { + result ++; + } + + return result; +} + +/* For each pair that the query subscribes for, count the occurrences in the + * table. Cardinality of subscribed for pairs must be the same as in the table + * or else the table won't match. */ +static +int32_t count_pairs( + const ecs_query_t *query, + ecs_type_t type) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + int32_t first_count = 0, pair_count = 0; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + + if (!ECS_HAS_ROLE(term->id, PAIR)) { + continue; + } + + if (term->args[0].entity != EcsThis) { + continue; + } + + if (ecs_id_is_wildcard(term->id)) { + pair_count = get_pair_count(type, term->id); + if (!first_count) { + first_count = pair_count; + } else { + if (first_count != pair_count) { + /* The pairs that this query subscribed for occur in the + * table but don't have the same cardinality. Ignore the + * table. This could typically happen for empty tables along + * a path in the table graph. */ + return -1; + } + } + } + } + + return first_count; +} + +static +ecs_type_t get_term_type( + ecs_world_t *world, + ecs_term_t *term, + ecs_entity_t component) +{ + ecs_oper_kind_t oper = term->oper; + + if (oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom) { + const EcsType *type = ecs_get(world, component, EcsType); + if (type) { + return type->normalized; + } else { + return ecs_get_type(world, component); + } + } else { + return ecs_type_from_id(world, component); + } +} + +/** Add table to system, compute offsets for system components in table it */ +static +void add_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_type_t table_type = NULL; + ecs_term_t *terms = query->filter.terms; + int32_t t, c, term_count = query->filter.term_count; + + if (table) { + table_type = table->type; + } + + int32_t pair_cur = 0, pair_count = count_pairs(query, table_type); + + /* If the query has pairs, we need to account for the fact that a table may + * have multiple components to which the pair is applied, which means the + * table has to be registered with the query multiple times, with different + * table columns. If so, allocate a small array for each pair in which the + * last added table index of the pair is stored, so that in the next + * iteration we can start the search from the correct offset type. */ + pair_offset_t *pair_offsets = NULL; + if (pair_count) { + pair_offsets = ecs_os_calloc( + ECS_SIZEOF(pair_offset_t) * term_count); + } + + /* From here we recurse */ + int32_t *table_indices = NULL; + int32_t table_indices_count = 0; + int32_t matched_table_index = 0; + ecs_matched_table_t table_data; + ecs_vector_t *references = NULL; + +add_pair: + table_data = (ecs_matched_table_t){ .table = table }; + if (table) { + table_type = table->type; + } + + /* If grouping is enabled for query, assign the group rank to the table */ + group_table(world, query, &table_data); + + if (term_count) { + /* Array that contains the system column to table column mapping */ + table_data.columns = ecs_os_calloc_n(int32_t, query->filter.term_count_actual); + ecs_assert(table_data.columns != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Store the components of the matched table. In the case of OR expressions, + * components may differ per matched table. */ + table_data.ids = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); + ecs_assert(table_data.ids != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Also cache types, so no lookup is needed while iterating */ + table_data.types = ecs_os_calloc_n(ecs_type_t, query->filter.term_count_actual); + ecs_assert(table_data.types != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Cache subject (source) entity ids for components */ + table_data.subjects = ecs_os_calloc_n(ecs_entity_t, query->filter.term_count_actual); + ecs_assert(table_data.subjects != NULL, ECS_OUT_OF_MEMORY, NULL); + + /* Cache subject (source) entity ids for components */ + table_data.sizes = ecs_os_calloc_n(ecs_size_t, query->filter.term_count_actual); + ecs_assert(table_data.sizes != NULL, ECS_OUT_OF_MEMORY, NULL); + } + + /* Walk columns parsed from the system signature */ + c = 0; + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + ecs_term_id_t subj = term->args[0]; + ecs_entity_t entity = 0, component = 0; + ecs_oper_kind_t op = term->oper; + + if (op == EcsNot) { + subj.entity = 0; + } + + /* Get actual component and component source for current column */ + t = get_comp_and_src(world, query, t, table, &component, &entity); + + /* This column does not retrieve data from a static entity */ + if (!entity && subj.entity) { + int32_t index = get_component_index(world, table, table_type, + &component, c, op, pair_offsets, pair_cur + 1); + + if (index == -1) { + if (op == EcsOptional && subj.set.mask == EcsSelf) { + index = 0; + } + } else { + if (op == EcsOptional && !(subj.set.mask & EcsSelf)) { + index = 0; + } + } + + table_data.columns[c] = index; + + /* If the column is a case, we should only iterate the entities in + * the column for this specific case. Add a sparse column with the + * case id so we can find the correct entities when iterating */ + if (ECS_HAS_ROLE(component, CASE)) { + flecs_sparse_column_t *sc = ecs_vector_add( + &table_data.sparse_columns, flecs_sparse_column_t); + sc->signature_column_index = t; + sc->sw_case = component & ECS_COMPONENT_MASK; + sc->sw_column = NULL; + } + + /* If table has a disabled bitmask for components, check if there is + * a disabled column for the queried for component. If so, cache it + * in a vector as the iterator will need to skip the entity when the + * component is disabled. */ + if (index && (table && table->flags & EcsTableHasDisabled)) { + ecs_entity_t bs_id = + (component & ECS_COMPONENT_MASK) | ECS_DISABLED; + int32_t bs_index = ecs_type_index_of(table->type, 0, bs_id); + if (bs_index != -1) { + flecs_bitset_column_t *elem = ecs_vector_add( + &table_data.bitset_columns, flecs_bitset_column_t); + elem->column_index = bs_index; + elem->bs_column = NULL; + } + } + } + + ecs_entity_t type_id = ecs_get_typeid(world, component); + + if (entity || table_data.columns[c] == -1 || subj.set.mask & EcsCascade) { + if (type_id) { + references = add_ref(world, query, references, term, + component, entity); + table_data.columns[c] = -ecs_vector_count(references); + } + + table_data.subjects[c] = entity; + flecs_set_watch(world, entity); + } + + if (type_id) { + const EcsComponent *cptr = ecs_get(world, type_id, EcsComponent); + if (!cptr || !cptr->size) { + int32_t column = table_data.columns[c]; + if (column < 0) { + ecs_ref_t *r = ecs_vector_get( + references, ecs_ref_t, -column - 1); + r->component = 0; + } + } + + if (cptr) { + table_data.sizes[c] = cptr->size; + } else { + table_data.sizes[c] = 0; + } + } else { + table_data.sizes[c] = 0; + } + + + if (ECS_HAS_ROLE(component, SWITCH)) { + table_data.sizes[c] = ECS_SIZEOF(ecs_entity_t); + } else if (ECS_HAS_ROLE(component, CASE)) { + table_data.sizes[c] = ECS_SIZEOF(ecs_entity_t); + } + + table_data.ids[c] = component; + table_data.types[c] = get_term_type(world, term, component); + + c ++; + } + + /* Initially always add table to inactive group. If the system is registered + * with the table and the table is not empty, the table will send an + * activate signal to the system. */ + + ecs_matched_table_t *table_elem; + if (table && has_auto_activation(query)) { + table_elem = ecs_vector_add(&query->empty_tables, + ecs_matched_table_t); + + /* Store table index */ + matched_table_index = ecs_vector_count(query->empty_tables); + table_indices_count ++; + table_indices = ecs_os_realloc( + table_indices, table_indices_count * ECS_SIZEOF(int32_t)); + table_indices[table_indices_count - 1] = -matched_table_index; + + #ifndef NDEBUG + char *type_expr = ecs_type_str(world, table->type); + ecs_trace_2("query #[green]%s#[reset] matched with table #[green][%s]", + query_name(world, query), type_expr); + ecs_os_free(type_expr); + #endif + } else { + /* If no table is provided to function, this is a system that contains + * no columns that require table matching. In this case, the system will + * only have one "dummy" table that caches data from the system columns. + * Always add this dummy table to the list of active tables, since it + * would never get activated otherwise. */ + table_elem = ecs_vector_add(&query->tables, ecs_matched_table_t); + + /* If query doesn't automatically activates/inactivates tables, we can + * get the count to determine the current table index. */ + matched_table_index = ecs_vector_count(query->tables) - 1; + ecs_assert(matched_table_index >= 0, ECS_INTERNAL_ERROR, NULL); + } + + if (references) { + ecs_size_t ref_size = ECS_SIZEOF(ecs_ref_t) * ecs_vector_count(references); + table_data.references = ecs_os_malloc(ref_size); + ecs_os_memcpy(table_data.references, + ecs_vector_first(references, ecs_ref_t), ref_size); + ecs_vector_free(references); + references = NULL; + } + + *table_elem = table_data; + + /* Use tail recursion when adding table for multiple pairs */ + pair_cur ++; + if (pair_cur < pair_count) { + goto add_pair; + } + + /* Register table indices before sending out the match signal. This signal + * can cause table activation, and table indices are needed for that. */ + if (table_indices) { + ecs_table_indices_t *ti = ecs_map_ensure( + query->table_indices, ecs_table_indices_t, table->id); + if (ti->indices) { + ecs_os_free(ti->indices); + } + ti->indices = table_indices; + ti->count = table_indices_count; + } + + if (table && !(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryMatch, + .query = query, + .matched_table_index = matched_table_index + }); + } else if (table && ecs_table_count(table)) { + activate_table(world, query, table, true); + } + + if (pair_offsets) { + ecs_os_free(pair_offsets); + } +} + +static +bool match_term( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_term_t *term, + ecs_match_failure_t *failure_info) +{ + (void)failure_info; + + ecs_term_id_t *subj = &term->args[0]; + + /* If term has no subject, there's nothing to match */ + if (!subj->entity) { + return true; + } + + if (term->args[0].entity != EcsThis) { + table = ecs_get_table(world, subj->entity); + } + + return ecs_type_match( + world, table, table->type, 0, term->id, subj->set.relation, + subj->set.min_depth, subj->set.max_depth, NULL) != -1; +} + +/* Match table with query */ +bool flecs_query_match( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_query_t *query, + ecs_match_failure_t *failure_info) +{ + /* Prevent having to add if not null checks everywhere */ + ecs_match_failure_t tmp_failure_info; + if (!failure_info) { + failure_info = &tmp_failure_info; + } + + failure_info->reason = EcsMatchOk; + failure_info->column = 0; + + if (!(query->flags & EcsQueryNeedsTables)) { + failure_info->reason = EcsMatchSystemIsATask; + return false; + } + + ecs_type_t table_type = table->type; + + /* Don't match disabled entities */ + if (!(query->flags & EcsQueryMatchDisabled) && ecs_type_has_id( + world, table_type, EcsDisabled, true)) + { + failure_info->reason = EcsMatchEntityIsDisabled; + return false; + } + + /* Don't match prefab entities */ + if (!(query->flags & EcsQueryMatchPrefab) && ecs_type_has_id( + world, table_type, EcsPrefab, true)) + { + failure_info->reason = EcsMatchEntityIsPrefab; + return false; + } + + /* Check if pair cardinality matches pairs in query, if any */ + if (count_pairs(query, table->type) == -1) { + return false; + } + + ecs_term_t *terms = query->filter.terms; + int32_t i, term_count = query->filter.term_count; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_oper_kind_t oper = term->oper; + + failure_info->column = i + 1; + + if (oper == EcsAnd) { + if (!match_term(world, table, term, failure_info)) { + return false; + } + + } else if (oper == EcsNot) { + if (match_term(world, table, term, failure_info)) { + return false; + } + + } else if (oper == EcsOr) { + bool match = false; + + for (; i < term_count; i ++) { + term = &terms[i]; + if (term->oper != EcsOr) { + i --; + break; + } + + if (!match && match_term( + world, table, term, failure_info)) + { + match = true; + } + } + + if (!match) { + return false; + } + + } else if (oper == EcsAndFrom || oper == EcsOrFrom || oper == EcsNotFrom) { + ecs_type_t type = get_term_type((ecs_world_t*)world, term, term->id); + int32_t match_count = 0, j, count = ecs_vector_count(type); + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + + for (j = 0; j < count; j ++) { + ecs_term_t tmp_term = *term; + tmp_term.oper = EcsAnd; + tmp_term.id = ids[j]; + tmp_term.pred.entity = ids[j]; + + if (match_term(world, table, &tmp_term, failure_info)) { + match_count ++; + } + } + + if (oper == EcsAndFrom && match_count != count) { + return false; + } + if (oper == EcsOrFrom && match_count == 0) { + return false; + } + if (oper == EcsNotFrom && match_count != 0) { + return false; + } + } + } + + return true; +} + +/** Match existing tables against system (table is created before system) */ +static +void match_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + int32_t i, count = flecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense( + world->store.tables, ecs_table_t, i); + + if (flecs_query_match(world, table, query, NULL)) { + add_table(world, query, table); + } + } + + order_grouped_tables(world, query); +} + +#define ELEM(ptr, size, index) ECS_OFFSET(ptr, size * index) + +static +int32_t qsort_partition( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t elem_size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t compare) +{ + int32_t p = (hi + lo) / 2; + void *pivot = 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 = ELEM(ptr, elem_size, i); + } while ( compare(entities[i], el, pivot_e, pivot) < 0); + + do { + j --; + el = ELEM(ptr, elem_size, j); + } while ( compare(entities[j], el, pivot_e, pivot) > 0); + + if (i >= j) { + return j; + } + + flecs_table_swap(world, table, data, i, j); + + if (p == i) { + pivot = ELEM(ptr, elem_size, j); + pivot_e = entities[j]; + } else if (p == j) { + pivot = ELEM(ptr, elem_size, i); + pivot_e = entities[i]; + } + + goto repeat; + } +} + +static +void qsort_array( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + ecs_entity_t *entities, + void *ptr, + int32_t size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t compare) +{ + if ((hi - lo) < 1) { + return; + } + + int32_t p = qsort_partition( + world, table, data, entities, ptr, size, lo, hi, compare); + + qsort_array(world, table, data, entities, ptr, size, lo, p, compare); + + qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare); +} + +static +void sort_table( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare) +{ + ecs_data_t *data = flecs_table_get_data(table); + if (!data || !data->entities) { + /* Nothing to sort */ + return; + } + + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; + } + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &data->columns[column_index]; + size = column->size; + ptr = ecs_vector_first_t(column->data, size, column->alignment); + } + + qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); +} + +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_matched_table_t *table; + 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 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 build_sorted_table_range( + ecs_query_t *query, + int32_t start, + int32_t end) +{ + ecs_world_t *world = query->world; + ecs_entity_t component = query->order_by_component; + ecs_order_by_action_t compare = query->order_by; + + /* Fetch data from all matched tables */ + ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t); + sort_helper_t *helper = ecs_os_malloc((end - start) * ECS_SIZEOF(sort_helper_t)); + + int i, to_sort = 0; + for (i = start; i < end; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + ecs_data_t *data = flecs_table_get_data(table); + ecs_vector_t *entities; + if (!data || !(entities = data->entities) || !ecs_table_count(table)) { + continue; + } + + int32_t index = ecs_type_index_of(table->type, 0, component); + if (index != -1) { + ecs_column_t *column = &data->columns[index]; + int16_t size = column->size; + int16_t align = column->alignment; + helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else if (component) { + /* Find component in prefab */ + ecs_entity_t base; + ecs_type_match(world, table, table->type, 0, component, + EcsIsA, 1, 0, &base); + + /* If a base was not found, the query should not have allowed using + * the component for sorting */ + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *cptr = ecs_get(world, component, EcsComponent); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + + helper[to_sort].ptr = ecs_get_id(world, base, component); + helper[to_sort].elem_size = cptr->size; + helper[to_sort].shared = true; + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; + } + + helper[to_sort].table = table_data; + helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } + + ecs_table_slice_t *cur = NULL; + + bool proceed; + do { + int32_t j, min = 0; + proceed = true; + + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } + } + + 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->table != cur_helper->table) { + cur = ecs_vector_add(&query->table_slices, ecs_table_slice_t); + ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); + cur->table = cur_helper->table; + cur->start_row = cur_helper->row; + cur->count = 1; + } else { + cur->count ++; + } + + cur_helper->row ++; + } while (proceed); + + ecs_os_free(helper); +} + +static +void build_sorted_tables( + ecs_query_t *query) +{ + /* Clean previous sorted tables */ + ecs_vector_free(query->table_slices); + query->table_slices = NULL; + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first(query->tables, ecs_matched_table_t); + ecs_matched_table_t *table = NULL; + + int32_t start = 0, rank = 0; + for (i = 0; i < count; i ++) { + table = &tables[i]; + if (rank != table->rank) { + if (start != i) { + build_sorted_table_range(query, start, i); + start = i; + } + rank = table->rank; + } + } + + if (start != i) { + build_sorted_table_range(query, start, i); + } +} + +static +bool tables_dirty( + ecs_query_t *query) +{ + if (query->needs_reorder) { + order_grouped_tables(query->world, query); + } + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first(query->tables, + ecs_matched_table_t); + bool is_dirty = false; + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + + if (!table_data->monitor) { + table_data->monitor = flecs_table_get_monitor(table); + is_dirty = true; + } + + int32_t *dirty_state = flecs_table_get_dirty_state(table); + int32_t t, type_count = table->column_count; + for (t = 0; t < type_count + 1; t ++) { + is_dirty = is_dirty || (dirty_state[t] != table_data->monitor[t]); + } + } + + is_dirty = is_dirty || (query->match_count != query->prev_match_count); + + return is_dirty; +} + +static +void tables_reset_dirty( + ecs_query_t *query) +{ + query->prev_match_count = query->match_count; + + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + + if (!table_data->monitor) { + /* If one table doesn't have a monitor, none of the tables will have + * a monitor, so early out. */ + return; + } + + int32_t *dirty_state = flecs_table_get_dirty_state(table); + int32_t t, type_count = table->column_count; + for (t = 0; t < type_count + 1; t ++) { + table_data->monitor[t] = dirty_state[t]; + } + } +} + +static +void sort_tables( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_order_by_action_t compare = query->order_by; + if (!compare) { + return; + } + + ecs_entity_t order_by_component = query->order_by_component; + + /* Iterate over active tables. Don't bother with inactive tables, since + * they're empty */ + int32_t i, count = ecs_vector_count(query->tables); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + bool tables_sorted = false; + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table_data = &tables[i]; + ecs_table_t *table = table_data->table; + + /* If no monitor had been created for the table yet, create it now */ + bool is_dirty = false; + if (!table_data->monitor) { + table_data->monitor = flecs_table_get_monitor(table); + + /* A new table is always dirty */ + is_dirty = true; + } + + int32_t *dirty_state = flecs_table_get_dirty_state(table); + + is_dirty = is_dirty || (dirty_state[0] != table_data->monitor[0]); + + int32_t index = -1; + if (order_by_component) { + /* Get index of sorted component. We only care if the component we're + * sorting on has changed or if entities have been added / re(moved) */ + index = ecs_type_index_of(table->type, 0, order_by_component); + if (index != -1) { + ecs_assert(index < ecs_vector_count(table->type), ECS_INTERNAL_ERROR, NULL); + is_dirty = is_dirty || (dirty_state[index + 1] != table_data->monitor[index + 1]); + } else { + /* Table does not contain component which means the sorted + * component is shared. Table does not need to be sorted */ + continue; + } + } + + /* Check both if entities have moved (element 0) or if the component + * we're sorting on has changed (index + 1) */ + if (is_dirty) { + /* Sort the table */ + sort_table(world, table, index, compare); + tables_sorted = true; + } + } + + if (tables_sorted || query->match_count != query->prev_match_count) { + build_sorted_tables(query); + query->match_count ++; /* Increase version if tables changed */ + } +} + +static +bool has_refs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + + if (term->oper == EcsNot && !subj->entity) { + /* Special case: if oper kind is Not and the query contained a + * shared expression, the expression is translated to FromEmpty to + * prevent resolving the ref */ + return true; + } else if (subj->entity && (subj->entity != EcsThis || subj->set.mask != EcsSelf)) { + /* If entity is not this, or if it can be substituted by other + * entities, the query can have references. */ + return true; + } + } + + return false; +} + +static +bool has_pairs( + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + if (ecs_id_is_wildcard(terms[i].id)) { + return true; + } + } + + return false; +} + +static +void register_monitors( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + + /* If component is requested with EcsCascade register component as a + * parent monitor. Parent monitors keep track of whether an entity moved + * in the hierarchy, which potentially requires the query to reorder its + * tables. + * Also register a regular component monitor for EcsCascade columns. + * This ensures that when the component used in the EcsCascade column + * is added or removed tables are updated accordingly*/ + if (subj->set.mask & EcsSuperSet && subj->set.mask & EcsCascade && + subj->set.relation != EcsIsA) + { + if (term->oper != EcsOr) { + if (term->args[0].set.relation != EcsIsA) { + flecs_monitor_register( + world, term->args[0].set.relation, term->id, query); + } + flecs_monitor_register(world, 0, term->id, query); + } + + /* FromAny also requires registering a monitor, as FromAny columns can + * be matched with prefabs. The only term kinds that do not require + * registering a monitor are FromOwned and FromEmpty. */ + } else if ((subj->set.mask & EcsSuperSet) || (subj->entity != EcsThis)){ + if (term->oper != EcsOr) { + flecs_monitor_register(world, 0, term->id, query); + } + } + }; +} + +static +void process_signature( + ecs_world_t *world, + ecs_query_t *query) +{ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *pred = &term->pred; + ecs_term_id_t *subj = &term->args[0]; + ecs_term_id_t *obj = &term->args[1]; + ecs_oper_kind_t op = term->oper; + ecs_inout_kind_t inout = term->inout; + + (void)pred; + (void)obj; + + /* Queries do not support variables */ + ecs_assert(pred->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + ecs_assert(subj->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + ecs_assert(obj->var != EcsVarIsVariable, + ECS_UNSUPPORTED, NULL); + + /* Queries do not support subset substitutions */ + ecs_assert(!(pred->set.mask & EcsSubSet), ECS_UNSUPPORTED, NULL); + ecs_assert(!(subj->set.mask & EcsSubSet), ECS_UNSUPPORTED, NULL); + ecs_assert(!(obj->set.mask & EcsSubSet), ECS_UNSUPPORTED, NULL); + + /* Superset/subset substitutions aren't supported for pred/obj */ + ecs_assert(pred->set.mask == EcsDefaultSet, ECS_UNSUPPORTED, NULL); + ecs_assert(obj->set.mask == EcsDefaultSet, ECS_UNSUPPORTED, NULL); + + if (subj->set.mask == EcsDefaultSet) { + subj->set.mask = EcsSelf; + } + + /* If self is not included in set, always start from depth 1 */ + if (!subj->set.min_depth && !(subj->set.mask & EcsSelf)) { + subj->set.min_depth = 1; + } + + if (inout != EcsIn) { + query->flags |= EcsQueryHasOutColumns; + } + + if (op == EcsOptional) { + query->flags |= EcsQueryHasOptional; + } + + if (!(query->flags & EcsQueryMatchDisabled)) { + if (op == EcsAnd || op == EcsOr || op == EcsOptional) { + if (term->id == EcsDisabled) { + query->flags |= EcsQueryMatchDisabled; + } + } + } + + if (!(query->flags & EcsQueryMatchPrefab)) { + if (op == EcsAnd || op == EcsOr || op == EcsOptional) { + if (term->id == EcsPrefab) { + query->flags |= EcsQueryMatchPrefab; + } + } + } + + if (subj->entity == EcsThis) { + query->flags |= EcsQueryNeedsTables; + } + + if (subj->set.mask & EcsCascade && term->oper == EcsOptional) { + /* Query can only have one cascade column */ + ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); + query->cascade_by = i + 1; + } + + if (subj->entity && subj->entity != EcsThis && + subj->set.mask == EcsSelf) + { + flecs_set_watch(world, term->args[0].entity); + } + } + + query->flags |= (ecs_flags32_t)(has_refs(query) * EcsQueryHasRefs); + query->flags |= (ecs_flags32_t)(has_pairs(query) * EcsQueryHasTraits); + + if (!(query->flags & EcsQueryIsSubquery)) { + register_monitors(world, query); + } +} + +static +bool match_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + if (flecs_query_match(world, table, query, NULL)) { + add_table(world, query, table); + return true; + } + return false; +} + +/* Move table from empty to non-empty list, or vice versa */ +static +int32_t move_table( + ecs_query_t *query, + ecs_table_t *table, + int32_t index, + ecs_vector_t **dst_array, + ecs_vector_t *src_array, + bool activate) +{ + (void)table; + + int32_t new_index = 0; + int32_t last_src_index = ecs_vector_count(src_array) - 1; + ecs_assert(last_src_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_matched_table_t *mt = ecs_vector_last(src_array, ecs_matched_table_t); + + /* The last table of the source array will be moved to the location of the + * table to move, do some bookkeeping to keep things consistent. */ + if (last_src_index) { + ecs_table_indices_t *ti = ecs_map_get(query->table_indices, + ecs_table_indices_t, mt->table->id); + + int i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t old_index = ti->indices[i]; + if (activate) { + if (old_index >= 0) { + /* old_index should be negative if activate is true, since + * we're moving from the empty list to the non-empty list. + * However, if the last table in the source array is also + * the table being moved, this can happen. */ + ecs_assert(table == mt->table, + ECS_INTERNAL_ERROR, NULL); + continue; + } + /* If activate is true, src = the empty list, and index should + * be negative. */ + old_index = old_index * -1 - 1; /* Normalize */ + } + + /* Ensure to update correct index, as there can be more than one */ + if (old_index == last_src_index) { + if (activate) { + ti->indices[i] = index * -1 - 1; + } else { + ti->indices[i] = index; + } + break; + } + } + + /* If the src array contains tables, there must be a table that will get + * moved. */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); + } else { + /* If last_src_index is 0, the table to move was the only table in the + * src array, so no other administration needs to be updated. */ + } + + /* Actually move the table. Only move from src to dst if we have a + * dst_array, otherwise just remove it from src. */ + if (dst_array) { + new_index = ecs_vector_count(*dst_array); + ecs_vector_move_index(dst_array, src_array, ecs_matched_table_t, index); + + /* Make sure table is where we expect it */ + mt = ecs_vector_last(*dst_array, ecs_matched_table_t); + ecs_assert(mt->table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_count(*dst_array) == (new_index + 1), + ECS_INTERNAL_ERROR, NULL); + } else { + ecs_vector_remove(src_array, ecs_matched_table_t, index); + } + + /* Ensure that src array has now one element less */ + ecs_assert(ecs_vector_count(src_array) == last_src_index, + ECS_INTERNAL_ERROR, NULL); + + /* Return new index for table */ + if (activate) { + /* Table is now active, index is positive */ + return new_index; + } else { + /* Table is now inactive, index is negative */ + return new_index * -1 - 1; + } +} + +/** Table activation happens when a table was or becomes empty. Deactivated + * tables are not considered by the system in the main loop. */ +static +void activate_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table, + bool active) +{ + ecs_vector_t *src_array, *dst_array; + int32_t activated = 0; + int32_t prev_dst_count = 0; + (void)world; + (void)prev_dst_count; /* Only used when built with systems module */ + + if (active) { + src_array = query->empty_tables; + dst_array = query->tables; + prev_dst_count = ecs_vector_count(dst_array); + } else { + src_array = query->tables; + dst_array = query->empty_tables; + } + + ecs_table_indices_t *ti = ecs_map_get( + query->table_indices, ecs_table_indices_t, table->id); + + if (ti) { + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t index = ti->indices[i]; + + if (index < 0) { + if (!active) { + /* If table is already inactive, no need to move */ + continue; + } + index = index * -1 - 1; + } else { + if (active) { + /* If table is already active, no need to move */ + continue; + } + } + + ecs_matched_table_t *mt = ecs_vector_get( + src_array, ecs_matched_table_t, index); + ecs_assert(mt->table == table, ECS_INTERNAL_ERROR, NULL); + (void)mt; + + activated ++; + + ti->indices[i] = move_table( + query, table, index, &dst_array, src_array, active); + } + + if (activated) { + /* Activate system if registered with query */ +#ifdef FLECS_SYSTEMS_H + if (query->system) { + int32_t dst_count = ecs_vector_count(dst_array); + if (active) { + if (!prev_dst_count && dst_count) { + ecs_system_activate(world, query->system, true, NULL); + } + } else if (ecs_vector_count(src_array) == 0) { + ecs_system_activate(world, query->system, false, NULL); + } + } +#endif + } + + if (active) { + query->tables = dst_array; + } else { + query->empty_tables = dst_array; + } + } + + if (!activated) { + /* Received an activate event for a table we're not matched with. This + * can only happen if this is a subquery */ + ecs_assert((query->flags & EcsQueryIsSubquery) != 0, + ECS_INTERNAL_ERROR, NULL); + return; + } + + /* Signal query it needs to reorder tables. Doing this in place could slow + * down scenario's where a large number of tables is matched with an ordered + * query. Since each table would trigger the activate signal, there would be + * as many sorts as added tables, vs. only one when ordering happens when an + * iterator is obtained. */ + query->needs_reorder = true; +} + +static +void add_subquery( + ecs_world_t *world, + ecs_query_t *parent, + ecs_query_t *subquery) +{ + ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); + *elem = subquery; + + /* Iterate matched tables, match them with subquery */ + ecs_matched_table_t *tables = ecs_vector_first(parent->tables, ecs_matched_table_t); + int32_t i, count = ecs_vector_count(parent->tables); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table = &tables[i]; + match_table(world, subquery, table->table); + activate_table(world, subquery, table->table, true); + } + + /* Do the same for inactive tables */ + tables = ecs_vector_first(parent->empty_tables, ecs_matched_table_t); + count = ecs_vector_count(parent->empty_tables); + + for (i = 0; i < count; i ++) { + ecs_matched_table_t *table = &tables[i]; + match_table(world, subquery, table->table); + } +} + +static +void notify_subqueries( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + if (query->subqueries) { + ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); + int32_t i, count = ecs_vector_count(query->subqueries); + + ecs_query_event_t sub_event = *event; + sub_event.parent_query = query; + + for (i = 0; i < count; i ++) { + ecs_query_t *sub = queries[i]; + flecs_query_notify(world, sub, &sub_event); + } + } +} + +static +void free_matched_table( + ecs_matched_table_t *table) +{ + ecs_os_free(table->columns); + ecs_os_free(table->ids); + ecs_os_free(table->subjects); + ecs_os_free(table->sizes); + ecs_os_free((ecs_vector_t**)table->types); + ecs_os_free(table->references); + ecs_os_free(table->sparse_columns); + ecs_os_free(table->bitset_columns); + ecs_os_free(table->monitor); +} + +/** Check if a table was matched with the system */ +static +ecs_table_indices_t* get_table_indices( + ecs_query_t *query, + ecs_table_t *table) +{ + return ecs_map_get(query->table_indices, ecs_table_indices_t, table->id); +} + +static +void resolve_cascade_subject( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_indices_t *ti, + const ecs_table_t *table, + ecs_type_t table_type) +{ + int32_t term_index = query->cascade_by - 1; + ecs_term_t *term = &query->filter.terms[term_index]; + + /* For each table entry, find the correct subject of a cascade term */ + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t table_data_index = ti->indices[i]; + ecs_matched_table_t *table_data; + + if (table_data_index >= 0) { + table_data = ecs_vector_get( + query->tables, ecs_matched_table_t, table_data_index); + } else { + table_data = ecs_vector_get( + query->empty_tables, ecs_matched_table_t, + -1 * table_data_index - 1); + } + + ecs_assert(table_data->references != 0, + ECS_INTERNAL_ERROR, NULL); + + /* Obtain reference index */ + int32_t *column_indices = table_data->columns; + int32_t ref_index = -column_indices[term_index] - 1; + + /* Obtain pointer to the reference data */ + ecs_ref_t *references = table_data->references; + + /* Find source for component */ + ecs_entity_t subject; + ecs_type_match(world, table, table_type, 0, term->id, + term->args[0].set.relation, 1, 0, &subject); + + /* If container was found, update the reference */ + if (subject) { + ecs_ref_t *ref = &references[ref_index]; + ecs_assert(ref->component == term->id, ECS_INTERNAL_ERROR, NULL); + + references[ref_index].entity = ecs_get_alive(world, subject); + table_data->subjects[term_index] = subject; + ecs_get_ref_w_id(world, ref, subject, term->id); + } else { + references[ref_index].entity = 0; + table_data->subjects[term_index] = 0; + } + } +} + +/* Remove table */ +static +void remove_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_vector_t *tables, + int32_t index, + bool empty) +{ + ecs_matched_table_t *mt = ecs_vector_get( + tables, ecs_matched_table_t, index); + if (!mt) { + /* Query was notified of a table it doesn't match with, this can only + * happen if query is a subquery. */ + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_assert(mt->table == table, ECS_INTERNAL_ERROR, NULL); + (void)table; + + /* Free table before moving, as the move will cause another table to occupy + * the memory of mt */ + free_matched_table(mt); + move_table(query, mt->table, index, NULL, tables, empty); +} + +static +void unmatch_table( + ecs_query_t *query, + ecs_table_t *table, + ecs_table_indices_t *ti) +{ + if (!ti) { + ti = get_table_indices(query, table); + if (!ti) { + return; + } + } + + int32_t i, count = ti->count; + for (i = 0; i < count; i ++) { + int32_t index = ti->indices[i]; + if (index < 0) { + index = index * -1 - 1; + remove_table(query, table, query->empty_tables, index, true); + } else { + remove_table(query, table, query->tables, index, false); + } + } + + ecs_os_free(ti->indices); + ecs_map_remove(query->table_indices, table->id); +} + +static +void rematch_table( + ecs_world_t *world, + ecs_query_t *query, + ecs_table_t *table) +{ + ecs_table_indices_t *match = get_table_indices(query, table); + + if (flecs_query_match(world, table, query, NULL)) { + /* If the table matches, and it is not currently matched, add */ + if (match == NULL) { + add_table(world, query, table); + + /* If table still matches and has cascade column, reevaluate the + * sources of references. This may have changed in case + * components were added/removed to container entities */ + } else if (query->cascade_by) { + resolve_cascade_subject(world, query, match, table, table->type); + + /* If query has optional columns, it is possible that a column that + * previously had data no longer has data, or vice versa. Do a full + * rematch to make sure data is consistent. */ + } else if (query->flags & EcsQueryHasOptional) { + unmatch_table(query, table, match); + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + add_table(world, query, table); + } + } else { + /* Table no longer matches, remove */ + if (match != NULL) { + unmatch_table(query, table, match); + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + } + } +} + +static +bool satisfy_constraints( + ecs_world_t *world, + const ecs_filter_t *filter) +{ + ecs_term_t *terms = filter->terms; + int32_t i, count = filter->term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t oper = term->oper; + + if (subj->entity != EcsThis && subj->set.mask & EcsSelf) { + ecs_type_t type = ecs_get_type(world, subj->entity); + + if (ecs_type_has_id(world, type, term->id, false)) { + if (oper == EcsNot) { + return false; + } + } else { + if (oper != EcsNot) { + return false; + } + } + } + } + + return true; +} + +/* Rematch system with tables after a change happened to a watched entity */ +static +void rematch_tables( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_t *parent_query) +{ + if (parent_query) { + ecs_matched_table_t *tables = ecs_vector_first(parent_query->tables, ecs_matched_table_t); + int32_t i, count = ecs_vector_count(parent_query->tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i].table; + rematch_table(world, query, table); + } + + tables = ecs_vector_first(parent_query->empty_tables, ecs_matched_table_t); + count = ecs_vector_count(parent_query->empty_tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = tables[i].table; + rematch_table(world, query, table); + } + } else { + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + /* Is the system currently matched with the table? */ + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + rematch_table(world, query, table); + } + } + + group_tables(world, query); + order_grouped_tables(world, query); + + /* Enable/disable system if constraints are (not) met. If the system is + * already dis/enabled this operation has no side effects. */ + query->constraints_satisfied = satisfy_constraints(world, &query->filter); +} + +static +void remove_subquery( + ecs_query_t *parent, + ecs_query_t *sub) +{ + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vector_count(parent->subqueries); + ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); + + for (i = 0; i < count; i ++) { + if (sq[i] == sub) { + break; + } + } + + ecs_vector_remove(parent->subqueries, ecs_query_t*, i); +} + +/* -- Private API -- */ + +void flecs_query_notify( + ecs_world_t *world, + ecs_query_t *query, + ecs_query_event_t *event) +{ + bool notify = true; + + switch(event->kind) { + case EcsQueryTableMatch: + /* Creation of new table */ + if (match_table(world, query, event->table)) { + if (query->subqueries) { + notify_subqueries(world, query, event); + } + } + notify = false; + break; + case EcsQueryTableUnmatch: + /* Deletion of table */ + unmatch_table(query, event->table, NULL); + break; + case EcsQueryTableRematch: + /* Rematch tables of query */ + rematch_tables(world, query, event->parent_query); + break; + case EcsQueryTableEmpty: + /* Table is empty, deactivate */ + activate_table(world, query, event->table, false); + break; + case EcsQueryTableNonEmpty: + /* Table is non-empty, activate */ + activate_table(world, query, event->table, true); + break; + case EcsQueryOrphan: + ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); + query->flags |= EcsQueryIsOrphaned; + query->parent = NULL; + break; + } + + if (notify) { + notify_subqueries(world, query, event); + } +} + +void ecs_query_order_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t order_by_component, + ecs_order_by_action_t order_by) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + + query->order_by_component = order_by_component; + query->order_by = order_by; + + ecs_vector_free(query->table_slices); + query->table_slices = NULL; + + sort_tables(world, query); + + if (!query->table_slices) { + build_sorted_tables(query); + } +} + +void ecs_query_group_by( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + ecs_assert(query->flags & EcsQueryNeedsTables, ECS_INVALID_PARAMETER, NULL); + + query->group_by_id = sort_component; + query->group_by = group_by; + + group_tables(world, query); + + order_grouped_tables(world, query); + + build_sorted_tables(query); +} + + +/* -- Public API -- */ + +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + ecs_query_t *result = flecs_sparse_add(world->queries, ecs_query_t); + result->id = flecs_sparse_last_id(world->queries); + + if (ecs_filter_init(world, &result->filter, &desc->filter)) { + flecs_sparse_remove(world->queries, result->id); + return NULL; + } + + result->world = world; + result->table_indices = ecs_map_new(ecs_table_indices_t, 0); + result->tables = ecs_vector_new(ecs_matched_table_t, 0); + result->empty_tables = ecs_vector_new(ecs_matched_table_t, 0); + result->system = desc->system; + result->prev_match_count = -1; + + if (desc->parent != NULL) { + result->flags |= EcsQueryIsSubquery; + } + + /* If a system is specified, ensure that if there are any subjects in the + * filter that refer to the system, the component is added */ + if (desc->system) { + int32_t t, term_count = result->filter.term_count; + ecs_term_t *terms = result->filter.terms; + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (term->args[0].entity == desc->system) { + ecs_add_id(world, desc->system, term->id); + } + } + } + + process_signature(world, result); + + ecs_trace_2("query #[green]%s#[reset] created with expression #[red]%s", + query_name(world, result), result->filter.expr); + + ecs_log_push(); + + if (!desc->parent) { + if (result->flags & EcsQueryNeedsTables) { + if (desc->system) { + if (ecs_has_id(world, desc->system, EcsMonitor)) { + result->flags |= EcsQueryMonitor; + } + + if (ecs_has_id(world, desc->system, EcsOnSet)) { + result->flags |= EcsQueryOnSet; + } + + if (ecs_has_id(world, desc->system, EcsUnSet)) { + result->flags |= EcsQueryUnSet; + } + } + + match_tables(world, result); + } else { + /* Add stub table that resolves references (if any) so everything is + * preprocessed when the query is evaluated. */ + add_table(world, result, NULL); + } + } else { + add_subquery(world, desc->parent, result); + result->parent = desc->parent; + } + + result->constraints_satisfied = satisfy_constraints(world, &result->filter); + + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + result->group_by = group_by_cascade; + result->group_by_id = result->filter.terms[cascade_by - 1].id; + result->group_by_ctx = &result->filter.terms[cascade_by - 1]; + } + + if (desc->order_by) { + ecs_query_order_by( + world, result, desc->order_by_component, desc->order_by); + } + + if (desc->group_by) { + /* Can't have a cascade term and group by at the same time, as cascade + * uses the group_by mechanism */ + ecs_assert(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); + ecs_query_group_by(world, result, desc->group_by_id, desc->group_by); + result->group_by_ctx = desc->group_by_ctx; + result->group_by_ctx_free = desc->group_by_ctx_free; + } + + ecs_log_pop(); + + return result; +} + +void ecs_query_fini( + ecs_query_t *query) +{ + ecs_world_t *world = query->world; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + + if (query->group_by_ctx_free) { + if (query->group_by_ctx) { + query->group_by_ctx_free(query->group_by_ctx); + } + } + + if ((query->flags & EcsQueryIsSubquery) && + !(query->flags & EcsQueryIsOrphaned)) + { + remove_subquery(query->parent, query); + } + + notify_subqueries(world, query, &(ecs_query_event_t){ + .kind = EcsQueryOrphan + }); + + ecs_vector_each(query->empty_tables, ecs_matched_table_t, table, { + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table->table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + free_matched_table(table); + }); + + ecs_vector_each(query->tables, ecs_matched_table_t, table, { + if (!(query->flags & EcsQueryIsSubquery)) { + flecs_table_notify(world, table->table, &(ecs_table_event_t){ + .kind = EcsTableQueryUnmatch, + .query = query + }); + } + free_matched_table(table); + }); + + ecs_map_iter_t it = ecs_map_iter(query->table_indices); + ecs_table_indices_t *ti; + while ((ti = ecs_map_next(&it, ecs_table_indices_t, NULL))) { + ecs_os_free(ti->indices); + } + + ecs_map_free(query->table_indices); + ecs_vector_free(query->subqueries); + ecs_vector_free(query->tables); + ecs_vector_free(query->empty_tables); + ecs_vector_free(query->table_slices); + ecs_filter_fini(&query->filter); + + /* Remove query from storage */ + flecs_sparse_remove(world->queries, query->id); +} + +const ecs_filter_t* ecs_query_get_filter( + ecs_query_t *query) +{ + return &query->filter; +} + +/* Create query iterator */ +ecs_iter_t ecs_query_iter_page( + ecs_query_t *query, + int32_t offset, + int32_t limit) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + + ecs_world_t *world = query->world; + + if (query->needs_reorder) { + order_grouped_tables(world, query); + } + + sort_tables(world, query); + + if (!world->is_readonly && query->flags & EcsQueryHasRefs) { + flecs_eval_component_monitors(world); + } + + tables_reset_dirty(query); + + int32_t table_count; + if (query->table_slices) { + table_count = ecs_vector_count(query->table_slices); + } else { + table_count = ecs_vector_count(query->tables); + } + + ecs_query_iter_t it = { + .page_iter = { + .offset = offset, + .limit = limit, + .remaining = limit + }, + .index = 0, + }; + + return (ecs_iter_t){ + .world = world, + .query = query, + .column_count = query->filter.term_count_actual, + .table_count = table_count, + .inactive_table_count = ecs_vector_count(query->empty_tables), + .iter.query = it + }; +} + +ecs_iter_t ecs_query_iter( + ecs_query_t *query) +{ + return ecs_query_iter_page(query, 0, 0); +} + +static +void populate_ptrs( + ecs_world_t *world, + ecs_iter_t *it) +{ + ecs_table_t *table = it->table; + const ecs_data_t *data = NULL; + ecs_column_t *columns = NULL; + ecs_id_t *ids = NULL; + + if (table) { + data = flecs_table_get_data(table); + ids = ecs_vector_first(table->type, ecs_id_t); + } + if (data) { + columns = data->columns; + } + + int c; + for (c = 0; c < it->column_count; c ++) { + int32_t c_index = it->columns[c]; + if (!c_index) { + it->ptrs[c] = NULL; + continue; + } + + if (c_index > 0) { + c_index --; + + if (!columns) { + continue; + } + + if (it->sizes[c] == 0) { + continue; + } + + ecs_vector_t *vec; + ecs_size_t size, align; + if (ECS_HAS_ROLE(ids[c_index], SWITCH)) { + ecs_switch_t *sw = data->sw_columns[ + c_index - table->sw_column_offset].data; + vec = flecs_switch_values(sw); + size = ECS_SIZEOF(ecs_entity_t); + align = ECS_ALIGNOF(ecs_entity_t); + } else { + ecs_column_t *col = &columns[c_index]; + vec = col->data; + size = col->size; + align = col->alignment; + } + + it->ptrs[c] = ecs_vector_get_t(vec, size, align, it->offset); + } else { + ecs_ref_t *ref = &it->references[-c_index - 1]; + char buf[255]; ecs_id_str(world, ref->component, buf, 255); + it->ptrs[c] = (void*)ecs_get_ref_w_id( + world, ref, ref->entity, ref->component); + } + } +} + +void flecs_query_set_iter( + ecs_world_t *world, + ecs_query_t *query, + ecs_iter_t *it, + int32_t table_index, + int32_t row, + int32_t count) +{ + (void)world; + + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + + ecs_matched_table_t *table_data = ecs_vector_get( + query->tables, ecs_matched_table_t, table_index); + ecs_assert(table_data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *table = table_data->table; + ecs_data_t *data = flecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entity_buffer = ecs_vector_first(data->entities, ecs_entity_t); + it->entities = &entity_buffer[row]; + + it->world = NULL; + it->query = query; + it->column_count = query->filter.term_count_actual; + it->table_count = 1; + it->inactive_table_count = 0; + it->table_columns = data->columns; + it->table = table; + it->ids = table_data->ids; + it->columns = table_data->columns; + it->types = table_data->types; + it->subjects = table_data->subjects; + it->sizes = table_data->sizes; + it->references = table_data->references; + it->offset = row; + it->count = count; + it->total_count = count; + + ecs_iter_init(it); + + populate_ptrs(world, it); +} + +static +int ecs_page_iter_next( + ecs_page_iter_t *it, + ecs_page_cursor_t *cur) +{ + int32_t offset = it->offset; + int32_t limit = it->limit; + if (!(offset || limit)) { + return cur->count == 0; + } + + int32_t count = cur->count; + int32_t remaining = it->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + it->offset -= count; + return 1; + } else { + cur->first += offset; + count = cur->count -= offset; + it->offset = 0; + } + } + + if (remaining) { + if (remaining > count) { + it->remaining -= count; + } else { + count = cur->count = remaining; + it->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + return -1; + } + + return count == 0; +} + +static +int find_smallest_column( + ecs_table_t *table, + ecs_matched_table_t *table_data, + ecs_vector_t *sparse_columns) +{ + flecs_sparse_column_t *sparse_column_array = + ecs_vector_first(sparse_columns, flecs_sparse_column_t); + int32_t i, count = ecs_vector_count(sparse_columns); + int32_t min = INT_MAX, index = 0; + + for (i = 0; i < count; i ++) { + /* The array with sparse queries for the matched table */ + flecs_sparse_column_t *sparse_column = &sparse_column_array[i]; + + /* Pointer to the switch column struct of the table */ + ecs_sw_column_t *sc = sparse_column->sw_column; + + /* If the sparse column pointer hadn't been retrieved yet, do it now */ + if (!sc) { + /* Get the table column index from the signature column index */ + int32_t table_column_index = table_data->columns[ + sparse_column->signature_column_index]; + + /* Translate the table column index to switch column index */ + table_column_index -= table->sw_column_offset; + ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + + /* Get the sparse column */ + ecs_data_t *data = flecs_table_get_data(table); + sc = sparse_column->sw_column = + &data->sw_columns[table_column_index - 1]; + } + + /* Find the smallest column */ + ecs_switch_t *sw = sc->data; + int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); + if (case_count < min) { + min = case_count; + index = i + 1; + } + } + + return index; +} + +static +int sparse_column_next( + ecs_table_t *table, + ecs_matched_table_t *matched_table, + ecs_vector_t *sparse_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) +{ + bool first_iteration = false; + int32_t sparse_smallest; + + if (!(sparse_smallest = iter->sparse_smallest)) { + sparse_smallest = iter->sparse_smallest = find_smallest_column( + table, matched_table, sparse_columns); + first_iteration = true; + } + + sparse_smallest -= 1; + + flecs_sparse_column_t *columns = ecs_vector_first( + sparse_columns, flecs_sparse_column_t); + flecs_sparse_column_t *column = &columns[sparse_smallest]; + ecs_switch_t *sw, *sw_smallest = column->sw_column->data; + ecs_entity_t case_smallest = column->sw_case; + + /* Find next entity to iterate in sparse column */ + int32_t first; + if (first_iteration) { + first = flecs_switch_first(sw_smallest, case_smallest); + } else { + first = flecs_switch_next(sw_smallest, iter->sparse_first); + } + + if (first == -1) { + goto done; + } + + /* Check if entity matches with other sparse columns, if any */ + int32_t i, count = ecs_vector_count(sparse_columns); + do { + for (i = 0; i < count; i ++) { + if (i == sparse_smallest) { + /* Already validated this one */ + continue; + } + + column = &columns[i]; + sw = column->sw_column->data; + + if (flecs_switch_get(sw, first) != column->sw_case) { + first = flecs_switch_next(sw_smallest, first); + if (first == -1) { + goto done; + } + } + } + } while (i != count); + + cur->first = iter->sparse_first = first; + cur->count = 1; + + return 0; +done: + /* Iterated all elements in the sparse list, we should move to the + * next matched table. */ + iter->sparse_smallest = 0; + iter->sparse_first = 0; + + return -1; +} + +#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) + +static +int bitset_column_next( + ecs_table_t *table, + ecs_vector_t *bitset_columns, + ecs_query_iter_t *iter, + ecs_page_cursor_t *cur) +{ + /* Precomputed single-bit test */ + static const uint64_t bitmask[64] = { + (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, + (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, + (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, + (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, + (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, + (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, + (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, + (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, + (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, + (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, + (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, + (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, + (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, + (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, + (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, + (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 + }; + + /* Precomputed test to verify if remainder of block is set (or not) */ + static const uint64_t bitmask_remain[64] = { + BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), + BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), + BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), + BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), + BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), + BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), + BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), + BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), + BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), + BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), + BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), + BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), + BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), + BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), + BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), + BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), + BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), + BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), + BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), + BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), + BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), + BS_MAX - (BS_MAX >> 1) + }; + + int32_t i, count = ecs_vector_count(bitset_columns); + flecs_bitset_column_t *columns = ecs_vector_first( + bitset_columns, flecs_bitset_column_t); + int32_t bs_offset = table->bs_column_offset; + + int32_t first = iter->bitset_first; + int32_t last = 0; + + for (i = 0; i < count; i ++) { + flecs_bitset_column_t *column = &columns[i]; + ecs_bs_column_t *bs_column = columns[i].bs_column; + + if (!bs_column) { + ecs_data_t *data = table->data; + int32_t index = column->column_index; + ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); + bs_column = &data->bs_columns[index - bs_offset]; + columns[i].bs_column = bs_column; + } + + ecs_bitset_t *bs = &bs_column->data; + int32_t bs_elem_count = bs->count; + int32_t bs_block = first >> 6; + int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; + + if (bs_block >= bs_block_count) { + goto done; + } + + uint64_t *data = bs->data; + int32_t bs_start = first & 0x3F; + + /* Step 1: find the first non-empty block */ + uint64_t v = data[bs_block]; + uint64_t remain = bitmask_remain[bs_start]; + while (!(v & remain)) { + /* If no elements are remaining, move to next block */ + if ((++bs_block) >= bs_block_count) { + /* No non-empty blocks left */ + goto done; + } + + bs_start = 0; + remain = BS_MAX; /* Test the full block */ + v = data[bs_block]; + } + + /* Step 2: find the first non-empty element in the block */ + while (!(v & bitmask[bs_start])) { + bs_start ++; + + /* Block was not empty, so bs_start must be smaller than 64 */ + ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); + } + + /* Step 3: Find number of contiguous enabled elements after start */ + int32_t bs_end = bs_start, bs_block_end = bs_block; + + remain = bitmask_remain[bs_end]; + while ((v & remain) == remain) { + bs_end = 0; + bs_block_end ++; + + if (bs_block_end == bs_block_count) { + break; + } + + v = data[bs_block_end]; + remain = BS_MAX; /* Test the full block */ + } + + /* Step 4: find remainder of enabled elements in current block */ + if (bs_block_end != bs_block_count) { + while ((v & bitmask[bs_end])) { + bs_end ++; + } + } + + /* Block was not 100% occupied, so bs_start must be smaller than 64 */ + ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); + + /* Step 5: translate to element start/end and make sure that each column + * range is a subset of the previous one. */ + first = bs_block * 64 + bs_start; + int32_t cur_last = bs_block_end * 64 + bs_end; + + /* No enabled elements found in table */ + if (first == cur_last) { + goto done; + } + + /* If multiple bitsets are evaluated, make sure each subsequent range + * is equal or a subset of the previous range */ + if (i) { + /* If the first element of a subsequent bitset is larger than the + * previous last value, start over. */ + if (first >= last) { + i = -1; + continue; + } + + /* Make sure the last element of the range doesn't exceed the last + * element of the previous range. */ + if (cur_last > last) { + cur_last = last; + } + } + + last = cur_last; + int32_t elem_count = last - first; + + /* Make sure last element doesn't exceed total number of elements in + * the table */ + if (elem_count > bs_elem_count) { + elem_count = bs_elem_count; + } + + cur->first = first; + cur->count = elem_count; + iter->bitset_first = first; + } + + /* Keep track of last processed element for iteration */ + iter->bitset_first = last; + + return 0; +done: + return -1; +} + +static +void mark_columns_dirty( + ecs_query_t *query, + ecs_matched_table_t *table_data) +{ + ecs_table_t *table = table_data->table; + + if (table && table->dirty_state) { + ecs_term_t *terms = query->filter.terms; + int32_t c = 0, i, count = query->filter.term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + + if (term->inout != EcsIn && (term->inout != EcsInOutDefault || + (subj->entity == EcsThis && subj->set.mask == EcsSelf))) + { + int32_t table_column = table_data->columns[c]; + if (table_column > 0 && table_column <= table->column_count) { + table->dirty_state[table_column] ++; + } + } + + if (terms[i].oper == EcsOr) { + do { + i ++; + } while ((i < count) && terms[i].oper == EcsOr); + } + + c ++; + } + } +} + +/* Return next table */ +bool ecs_query_next( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_iter_t *iter = &it->iter.query; + ecs_page_iter_t *piter = &iter->page_iter; + ecs_query_t *query = it->query; + ecs_world_t *world = query->world; + (void)world; + + it->is_valid = true; + + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + if (!query->constraints_satisfied) { + goto done; + } + + ecs_table_slice_t *slice = ecs_vector_first( + query->table_slices, ecs_table_slice_t); + ecs_matched_table_t *tables = ecs_vector_first( + query->tables, ecs_matched_table_t); + + ecs_assert(!slice || query->order_by, ECS_INTERNAL_ERROR, NULL); + + ecs_page_cursor_t cur; + int32_t table_count = it->table_count; + int32_t prev_count = it->total_count; + + int i; + for (i = iter->index; i < table_count; i ++) { + ecs_matched_table_t *table_data = slice ? slice[i].table : &tables[i]; + ecs_table_t *table = table_data->table; + ecs_data_t *data = NULL; + + iter->index = i + 1; + + if (table) { + ecs_vector_t *bitset_columns = table_data->bitset_columns; + ecs_vector_t *sparse_columns = table_data->sparse_columns; + data = flecs_table_get_data(table); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + it->table_columns = data->columns; + + if (slice) { + cur.first = slice[i].start_row; + cur.count = slice[i].count; + } else { + cur.first = 0; + cur.count = ecs_table_count(table); + } + + if (cur.count) { + if (bitset_columns) { + if (bitset_column_next(table, bitset_columns, iter, + &cur) == -1) + { + /* No more enabled components for table */ + continue; + } else { + iter->index = i; + } + } + + if (sparse_columns) { + if (sparse_column_next(table, table_data, + sparse_columns, iter, &cur) == -1) + { + /* No more elements in sparse column */ + continue; + } else { + iter->index = i; + } + } + + int ret = ecs_page_iter_next(piter, &cur); + if (ret < 0) { + goto done; + } else if (ret > 0) { + continue; + } + } else { + continue; + } + + ecs_entity_t *entity_buffer = ecs_vector_first( + data->entities, ecs_entity_t); + it->entities = &entity_buffer[cur.first]; + it->offset = cur.first; + it->count = cur.count; + it->total_count = cur.count; + } + + it->table = table_data->table; + it->ids = table_data->ids; + it->columns = table_data->columns; + it->types = table_data->types; + it->subjects = table_data->subjects; + it->sizes = table_data->sizes; + it->references = table_data->references; + it->frame_offset += prev_count; + + ecs_iter_init(it); + + populate_ptrs(world, it); + + if (query->flags & EcsQueryHasOutColumns) { + if (table) { + mark_columns_dirty(query, table_data); + } + } + + goto yield; + } + +done: + ecs_iter_fini(it); + return false; + +yield: + return true; +} + +bool ecs_query_next_w_filter( + ecs_iter_t *iter, + const ecs_filter_t *filter) +{ + ecs_table_t *table; + + do { + if (!ecs_query_next(iter)) { + return false; + } + table = iter->table; + } while (filter && !flecs_table_match_filter(iter->world, table, filter)); + + return true; +} + +bool ecs_query_next_worker( + ecs_iter_t *it, + int32_t current, + int32_t total) +{ + int32_t per_worker, first, prev_offset = it->offset; + + do { + if (!ecs_query_next(it)) { + return false; + } + + int32_t count = it->count; + per_worker = count / total; + first = per_worker * current; + + count -= per_worker * total; + + if (count) { + if (current < count) { + per_worker ++; + first += current; + } else { + first += count; + } + } + + if (!per_worker && !(it->query->flags & EcsQueryNeedsTables)) { + if (current == 0) { + populate_ptrs(it->world, it); + return true; + } else { + return false; + } + } + } while (!per_worker); + + it->frame_offset -= prev_offset; + it->count = per_worker; + it->offset += first; + it->entities = &it->entities[first]; + it->frame_offset += first; + + populate_ptrs(it->world, it); + + return true; +} + +bool ecs_query_changed( + ecs_query_t *query) +{ + ecs_assert(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); + return tables_dirty(query); +} + +bool ecs_query_orphaned( + ecs_query_t *query) +{ + return query->flags & EcsQueryIsOrphaned; +} + diff --git a/fggl/ecs2/flecs/src/sparse.c b/fggl/ecs2/flecs/src/sparse.c new file mode 100644 index 0000000000000000000000000000000000000000..4df28ba73943c5b4807444a2ef992682af20fc5e --- /dev/null +++ b/fggl/ecs2/flecs/src/sparse.c @@ -0,0 +1,789 @@ +#include "private_api.h" + +/** The number of elements in a single chunk */ +#define CHUNK_COUNT (4096) + +/** Compute the chunk index from an id by stripping the first 12 bits */ +#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) + +/** This computes the offset of an index inside a chunk */ +#define OFFSET(index) ((int32_t)index & 0xFFF) + +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + +typedef struct chunk_t { + int32_t *sparse; /* Sparse array with indices to dense array */ + void *data; /* Store data in sparse array to reduce + * indirection and provide stable pointers. */ +} chunk_t; + +struct ecs_sparse_t { + ecs_vector_t *dense; /* Dense array with indices to sparse array. The + * dense array stores both alive and not alive + * sparse indices. The 'count' member keeps + * track of which indices are alive. */ + + ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id_local; /* Local max index (if no global is set) */ + uint64_t *max_id; /* Maximum issued sparse index */ +}; + +static +chunk_t* chunk_new( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + int32_t count = ecs_vector_count(sparse->chunks); + chunk_t *chunks; + + if (count <= chunk_index) { + ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1); + chunks = ecs_vector_first(sparse->chunks, chunk_t); + ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t)); + } else { + chunks = ecs_vector_first(sparse->chunks, chunk_t); + } + + ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); + + chunk_t *result = &chunks[chunk_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); + + /* Initialize sparse array with zero's, as zero is used to indicate that the + * sparse element has not been paired with a dense element. Use zero + * as this means we can take advantage of calloc having a possibly better + * performance than malloc + memset. */ + result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT); + + /* Initialize the data array with zero's to guarantee that data is + * always initialized. When an entry is removed, data is reset back to + * zero. Initialize now, as this can take advantage of calloc. */ + result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT); + + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +static +void chunk_free( + chunk_t *chunk) +{ + ecs_os_free(chunk->sparse); + ecs_os_free(chunk->data); +} + +static +chunk_t* get_chunk( + const ecs_sparse_t *sparse, + int32_t chunk_index) +{ + /* If chunk_index is below zero, application used an invalid entity id */ + ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL); + chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index); + if (result && !result->sparse) { + return NULL; + } + + return result; +} + +static +chunk_t* get_or_create_chunk( + ecs_sparse_t *sparse, + int32_t chunk_index) +{ + chunk_t *chunk = get_chunk(sparse, chunk_index); + if (chunk) { + return chunk; + } + + return chunk_new(sparse, chunk_index); +} + +static +void grow_dense( + ecs_sparse_t *sparse) +{ + ecs_vector_add(&sparse->dense, uint64_t); +} + +static +uint64_t strip_generation( + uint64_t *index_out) +{ + uint64_t index = *index_out; + uint64_t gen = index & ECS_GENERATION_MASK; + /* Make sure there's no junk in the id */ + ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), + ECS_INVALID_PARAMETER, NULL); + *index_out -= gen; + return gen; +} + +static +void assign_index( + chunk_t * chunk, + uint64_t * dense_array, + uint64_t index, + int32_t dense) +{ + /* Initialize sparse-dense pair. This assigns the dense index to the sparse + * array, and the sparse index to the dense array .*/ + chunk->sparse[OFFSET(index)] = dense; + dense_array[dense] = index; +} + +static +uint64_t inc_gen( + uint64_t index) +{ + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); +} + +static +uint64_t inc_id( + ecs_sparse_t *sparse) +{ + /* Generate a new id. The last issued id could be stored in an external + * variable, such as is the case with the last issued entity id, which is + * stored on the world. */ + return ++ (sparse->max_id[0]); +} + +static +uint64_t get_id( + const ecs_sparse_t *sparse) +{ + return sparse->max_id[0]; +} + +static +void set_id( + ecs_sparse_t *sparse, + uint64_t value) +{ + /* Sometimes the max id needs to be assigned directly, which typically + * happens when the API calls get_or_create for an id that hasn't been + * issued before. */ + sparse->max_id[0] = value; +} + +/* Pair dense id with new sparse id */ +static +uint64_t create_id( + ecs_sparse_t *sparse, + int32_t dense) +{ + uint64_t index = inc_id(sparse); + grow_dense(sparse); + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + assign_index(chunk, dense_array, index, dense); + + return index; +} + +/* Create new id */ +static +uint64_t new_index( + ecs_sparse_t *sparse) +{ + ecs_vector_t *dense = sparse->dense; + int32_t dense_count = ecs_vector_count(dense); + int32_t count = sparse->count ++; + + 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_vector_first(dense, uint64_t); + return dense_array[count]; + } else { + return create_id(sparse, count); + } +} + +/* Try obtaining a value from the sparse set, don't care about whether the + * provided index matches the current generation count. */ +static +void* try_sparse_any( + const ecs_sparse_t *sparse, + uint64_t index) +{ + strip_generation(&index); + + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); +} + +/* Try obtaining a value from the sparse set, make sure it's alive. */ +static +void* try_sparse( + const ecs_sparse_t *sparse, + uint64_t index) +{ + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + uint64_t gen = strip_generation(&index); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + + if (cur_gen != gen) { + return NULL; + } + + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, sparse->size, offset); +} + +/* Get value from sparse set when it is guaranteed that the value exists. This + * function is used when values are obtained using a dense index */ +static +void* get_sparse( + const ecs_sparse_t *sparse, + int32_t dense, + uint64_t index) +{ + strip_generation(&index); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; + + return DATA(chunk->data, sparse->size, offset); +} + +/* Swap dense elements. A swap occurs when an element is removed, or when a + * removed element is recycled. */ +static +void swap_dense( + ecs_sparse_t * sparse, + chunk_t * chunk_a, + int32_t a, + int32_t b) +{ + ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t index_a = dense_array[a]; + uint64_t index_b = dense_array[b]; + + chunk_t *chunk_b = get_or_create_chunk(sparse, CHUNK(index_b)); + assign_index(chunk_a, dense_array, index_a, b); + assign_index(chunk_b, dense_array, index_b, a); +} + +ecs_sparse_t* _flecs_sparse_new( + ecs_size_t size) +{ + ecs_sparse_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_sparse_t)); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + result->size = size; + result->max_id_local = UINT64_MAX; + result->max_id = &result->max_id_local; + + /* Consume first value in dense array as 0 is used in the sparse array to + * indicate that a sparse element hasn't been paired yet. */ + uint64_t *first = ecs_vector_add(&result->dense, uint64_t); + *first = 0; + + result->count = 1; + + return result; +} + +void flecs_sparse_set_id_source( + ecs_sparse_t * sparse, + uint64_t * id_source) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + sparse->max_id = id_source; +} + +void flecs_sparse_clear( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_vector_each(sparse->chunks, chunk_t, chunk, { + chunk_free(chunk); + }); + + ecs_vector_free(sparse->chunks); + ecs_vector_set_count(&sparse->dense, uint64_t, 1); + + sparse->chunks = NULL; + sparse->count = 1; + sparse->max_id_local = 0; +} + +void flecs_sparse_free( + ecs_sparse_t *sparse) +{ + if (sparse) { + flecs_sparse_clear(sparse); + ecs_vector_free(sparse->dense); + ecs_os_free(sparse); + } +} + +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return new_index(sparse); +} + +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t new_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t dense_count = ecs_vector_count(sparse->dense); + int32_t count = sparse->count; + int32_t remaining = dense_count - count; + int32_t i, to_create = new_count - remaining; + + if (to_create > 0) { + flecs_sparse_set_size(sparse, dense_count + to_create); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + + for (i = 0; i < to_create; i ++) { + uint64_t index = create_id(sparse, count + i); + dense_array[dense_count + i] = index; + } + } + + sparse->count += new_count; + + return ecs_vector_get(sparse->dense, uint64_t, count); +} + +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 = new_index(sparse); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(chunk->data, size, OFFSET(index)); +} + +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; +} + +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_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint64_t gen = strip_generation(&index); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + /* Check if element is alive. If element is not alive, update indices so + * that the first unused dense element points to the sparse element. */ + int32_t count = sparse->count; + if (dense == count) { + /* If dense is the next unused element in the array, simply increase + * the count to make it part of the alive set. */ + sparse->count ++; + } else if (dense > count) { + /* If dense is not alive, swap it with the first unused element. */ + swap_dense(sparse, chunk, dense, count); + + /* First unused element is now last used element */ + sparse->count ++; + } 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. */ + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); + (void)dense_vector; + (void)dense_array; + } else { + /* Element is not paired yet. Must add a new element to dense array */ + grow_dense(sparse); + + ecs_vector_t *dense_vector = sparse->dense; + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + int32_t dense_count = ecs_vector_count(dense_vector) - 1; + int32_t count = sparse->count ++; + + /* If index is larger than max id, update max id */ + if (index >= get_id(sparse)) { + set_id(sparse, index + 1); + } + + if (count < dense_count) { + /* If there are unused elements in the list, move the first unused + * element to the end of the list */ + uint64_t unused = dense_array[count]; + chunk_t *unused_chunk = get_or_create_chunk(sparse, CHUNK(unused)); + assign_index(unused_chunk, dense_array, unused, dense_count); + } + + assign_index(chunk, dense_array, index, count); + dense_array[count] |= gen; + } + + return DATA(chunk->data, sparse->size, offset); +} + +void* _flecs_sparse_set( + ecs_sparse_t * sparse, + ecs_size_t elem_size, + uint64_t index, + void * value) +{ + void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); + ecs_os_memcpy(ptr, value, elem_size); + return ptr; +} + +void* _flecs_sparse_remove_get( + 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; + + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + uint64_t gen = strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (gen != cur_gen) { + /* Generation doesn't match which means that the provided entity is + * already not alive. */ + return NULL; + } + + /* Increase generation */ + dense_array[dense] = index | inc_gen(cur_gen); + + int32_t count = sparse->count; + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + swap_dense(sparse, chunk, dense, count - 1); + sparse->count --; + } else { + /* Element is not alive, nothing to be done */ + return NULL; + } + + /* Reset memory to zero on remove */ + return DATA(chunk->data, sparse->size, offset); + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return NULL; + } +} + +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t index) +{ + void *ptr = _flecs_sparse_remove_get(sparse, 0, index); + if (ptr) { + ecs_os_memset(ptr, 0, sparse->size); + } +} + +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); + + uint64_t index_w_gen = index; + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + if (dense) { + /* Increase generation */ + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + dense_array[dense] = index_w_gen; + } else { + /* Element is not paired and thus not alive, nothing to be done */ + } +} + +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return false; + } + + strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + + return dense != 0; +} + +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_vector_first(sparse->dense, uint64_t); + return get_sparse(sparse, dense_index, dense_array[dense_index]); +} + +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) +{ + return try_sparse(sparse, index) != NULL; +} + +uint64_t flecs_sparse_get_alive( + const ecs_sparse_t *sparse, + uint64_t index) +{ + chunk_t *chunk = get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return 0; + } + + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + + /* If dense is 0 (tombstone) this will return 0 */ + return dense_array[dense]; +} + +void* _flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse(sparse, index); +} + +void* _flecs_sparse_get_any( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + return try_sparse_any(sparse, index); +} + +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) +{ + if (!sparse) { + return 0; + } + + return sparse->count - 1; +} + +int32_t flecs_sparse_size( + const ecs_sparse_t *sparse) +{ + if (!sparse) { + return 0; + } + + return ecs_vector_count(sparse->dense) - 1; +} + +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return &(ecs_vector_first(sparse->dense, uint64_t)[1]); +} + +void flecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); +} + +static +void sparse_copy( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + flecs_sparse_set_size(dst, flecs_sparse_size(src)); + const uint64_t *indices = flecs_sparse_ids(src); + + ecs_size_t size = src->size; + int32_t i, count = src->count; + + for (i = 0; i < count - 1; i ++) { + uint64_t index = indices[i]; + void *src_ptr = _flecs_sparse_get(src, size, index); + void *dst_ptr = _flecs_sparse_ensure(dst, size, index); + flecs_sparse_set_generation(dst, index); + ecs_os_memcpy(dst_ptr, src_ptr, size); + } + + set_id(dst, get_id(src)); + + ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); +} + +ecs_sparse_t* flecs_sparse_copy( + const ecs_sparse_t *src) +{ + if (!src) { + return NULL; + } + + ecs_sparse_t *dst = _flecs_sparse_new(src->size); + sparse_copy(dst, src); + + return dst; +} + +void flecs_sparse_restore( + ecs_sparse_t * dst, + const ecs_sparse_t * src) +{ + ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); + dst->count = 1; + if (src) { + sparse_copy(dst, src); + } +} + +void flecs_sparse_memory( + ecs_sparse_t *sparse, + int32_t *allocd, + int32_t *used) +{ + (void)sparse; + (void)allocd; + (void)used; +} + +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size) +{ + return _flecs_sparse_new(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); +} diff --git a/fggl/ecs2/flecs/src/stage.c b/fggl/ecs2/flecs/src/stage.c new file mode 100644 index 0000000000000000000000000000000000000000..8f7c17c1a5428e848c92770c4e646615008637d8 --- /dev/null +++ b/fggl/ecs2/flecs/src/stage.c @@ -0,0 +1,690 @@ +#include "private_api.h" + +static +ecs_op_t* new_defer_op(ecs_stage_t *stage) { + ecs_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_op_t); + ecs_os_memset(result, 0, ECS_SIZEOF(ecs_op_t)); + return result; +} + +static +void new_defer_component_ids( + ecs_op_t *op, + const ecs_ids_t *components) +{ + ecs_assert(components != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t components_count = components->count; + if (components_count == 1) { + ecs_entity_t component = components->array[0]; + op->component = component; + op->components = (ecs_ids_t) { + .array = NULL, + .count = 1 + }; + } else if (components_count) { + ecs_size_t array_size = components_count * ECS_SIZEOF(ecs_entity_t); + op->components.array = ecs_os_malloc(array_size); + ecs_os_memcpy(op->components.array, components->array, array_size); + op->components.count = components_count; + } else { + op->component = 0; + op->components = (ecs_ids_t){ 0 }; + } +} + +static +bool defer_add_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_ids_t *components) +{ + if (stage->defer) { + if (components) { + if (!components->count) { + return true; + } + } + + ecs_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->is._1.entity = entity; + + new_defer_component_ids(op, components); + + if (op_kind == EcsOpNew) { + world->new_count ++; + } else if (op_kind == EcsOpAdd) { + world->add_count ++; + } else if (op_kind == EcsOpRemove) { + world->remove_count ++; + } + + return true; + } else { + stage->defer ++; + } + + return false; +} + +static +void merge_stages( + ecs_world_t *world, + bool force_merge) +{ + bool is_stage = world->magic == ECS_STAGE_MAGIC; + ecs_stage_t *stage = flecs_stage_from_world(&world); + + bool measure_frame_time = world->measure_frame_time; + + ecs_time_t t_start; + if (measure_frame_time) { + ecs_os_get_time(&t_start); + } + + if (is_stage) { + /* Check for consistency if force_merge is enabled. In practice this + * function will never get called with force_merge disabled for just + * a single stage. */ + if (force_merge || stage->auto_merge) { + ecs_defer_end((ecs_world_t*)stage); + } + } else { + /* Merge stages. Only merge if the stage has auto_merging turned on, or + * if this is a forced merge (like when ecs_merge is called) */ + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(s->magic == ECS_STAGE_MAGIC, ECS_INTERNAL_ERROR, NULL); + if (force_merge || s->auto_merge) { + ecs_defer_end((ecs_world_t*)s); + } + } + } + + flecs_eval_component_monitors(world); + + if (measure_frame_time) { + world->stats.merge_time_total += + (FLECS_FLOAT)ecs_time_measure(&t_start); + } + + world->stats.merge_count_total ++; + + /* If stage is asynchronous, deferring is always enabled */ + if (stage->asynchronous) { + ecs_defer_begin((ecs_world_t*)stage); + } +} + +static +void do_auto_merge( + ecs_world_t *world) +{ + merge_stages(world, false); +} + +static +void do_manual_merge( + ecs_world_t *world) +{ + merge_stages(world, true); +} + +bool flecs_defer_none( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + return (++ stage->defer) == 1; +} + +bool flecs_defer_modified( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpModified; + op->component = component; + op->is._1.entity = entity; + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool flecs_defer_clone( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpClone; + op->component = src; + op->is._1.entity = entity; + op->is._1.clone_value = clone_value; + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool flecs_defer_delete( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpDelete; + op->is._1.entity = entity; + world->delete_count ++; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool flecs_defer_clear( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpClear; + op->is._1.entity = entity; + world->clear_count ++; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool flecs_defer_enable( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable) +{ + (void)world; + if (stage->defer) { + ecs_op_t *op = new_defer_op(stage); + op->kind = enable ? EcsOpEnable : EcsOpDisable; + op->is._1.entity = entity; + op->component = component; + return true; + } else { + stage->defer ++; + } + return false; +} + +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + const ecs_ids_t *components_ids, + void **component_data, + const ecs_entity_t **ids_out) +{ + if (stage->defer) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); + void **defer_data = NULL; + + world->bulk_new_count ++; + + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new_id(world); + } + + /* Create private copy for component data */ + if (component_data) { + int c, c_count = components_ids->count; + ecs_entity_t *components = components_ids->array; + defer_data = ecs_os_malloc(ECS_SIZEOF(void*) * c_count); + for (c = 0; c < c_count; c ++) { + ecs_entity_t comp = components[c]; + const EcsComponent *cptr = flecs_component_from_id(world, comp); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t size = cptr->size; + void *data = ecs_os_malloc(size * count); + defer_data[c] = data; + + const ecs_type_info_t *cinfo = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, comp); + if (real_id) { + cinfo = flecs_get_c_info(world, real_id); + } + ecs_xtor_t ctor; + if (cinfo && (ctor = cinfo->lifecycle.ctor)) { + void *ctx = cinfo->lifecycle.ctx; + ctor(world, comp, ids, data, flecs_to_size_t(size), count, ctx); + ecs_move_t move; + if ((move = cinfo->lifecycle.move)) { + move(world, comp, ids, ids, data, component_data[c], + flecs_to_size_t(size), count, ctx); + } else { + ecs_os_memcpy(data, component_data[c], size * count); + } + } else { + ecs_os_memcpy(data, component_data[c], size * count); + } + } + } + + /* Store data in op */ + ecs_op_t *op = new_defer_op(stage); + op->kind = EcsOpBulkNew; + op->is._n.entities = ids; + op->is._n.bulk_data = defer_data; + op->is._n.count = count; + new_defer_component_ids(op, components_ids); + *ids_out = ids; + + return true; + } else { + stage->defer ++; + } + + return false; +} + +bool flecs_defer_new( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components) +{ + return defer_add_remove(world, stage, EcsOpNew, entity, components); +} + +bool flecs_defer_add( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components) +{ + return defer_add_remove(world, stage, EcsOpAdd, entity, components); +} + +bool flecs_defer_remove( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_ids_t *components) +{ + return defer_add_remove(world, stage, EcsOpRemove, entity, components); +} + +bool flecs_defer_set( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_op_kind_t op_kind, + ecs_entity_t entity, + ecs_entity_t component, + ecs_size_t size, + const void *value, + void **value_out, + bool *is_added) +{ + if (stage->defer) { + world->set_count ++; + if (!size) { + const EcsComponent *cptr = flecs_component_from_id(world, component); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); + size = cptr->size; + } + + ecs_op_t *op = new_defer_op(stage); + op->kind = op_kind; + op->component = component; + op->is._1.entity = entity; + op->is._1.size = size; + op->is._1.value = ecs_os_malloc(size); + + if (!value) { + value = ecs_get_id(world, entity, component); + if (is_added) { + *is_added = value == NULL; + } + } + + const ecs_type_info_t *c_info = NULL; + ecs_entity_t real_id = ecs_get_typeid(world, component); + if (real_id) { + c_info = flecs_get_c_info(world, real_id); + } + + if (value) { + ecs_copy_ctor_t copy; + if (c_info && (copy = c_info->lifecycle.copy_ctor)) { + copy(world, component, &c_info->lifecycle, &entity, &entity, + op->is._1.value, value, flecs_to_size_t(size), 1, + c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(op->is._1.value, value, size); + } + } else { + ecs_xtor_t ctor; + if (c_info && (ctor = c_info->lifecycle.ctor)) { + ctor(world, component, &entity, op->is._1.value, + flecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } + } + + if (value_out) { + *value_out = op->is._1.value; + } + + return true; + } else { + stage->defer ++; + } + + return false; +} + +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) +{ + /* Execute post frame actions */ + ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { + action->action(world, action->ctx); + }); + + ecs_vector_free(stage->post_frame_actions); + stage->post_frame_actions = NULL; +} + +void flecs_stage_init( + ecs_world_t *world, + ecs_stage_t *stage) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + memset(stage, 0, sizeof(ecs_stage_t)); + + stage->magic = ECS_STAGE_MAGIC; + stage->world = world; + stage->thread_ctx = world; + stage->auto_merge = true; + stage->asynchronous = false; +} + +void flecs_stage_deinit( + ecs_world_t *world, + ecs_stage_t *stage) +{ + (void)world; + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(stage->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vector_count(stage->defer_queue) == 0, + ECS_INVALID_PARAMETER, NULL); + + /* Set magic to 0 so that accessing the stage after deinitializing it will + * throw an assert. */ + stage->magic = 0; + + ecs_vector_free(stage->defer_queue); +} + +void ecs_set_stages( + ecs_world_t *world, + int32_t stage_count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stages; + int32_t i, count = ecs_vector_count(world->worker_stages); + + if (count && count != stage_count) { + stages = ecs_vector_first(world->worker_stages, ecs_stage_t); + + for (i = 0; i < count; i ++) { + /* If stage contains a thread handle, ecs_set_threads was used to + * create the stages. ecs_set_threads and ecs_set_stages should not + * be mixed. */ + ecs_assert(stages[i].magic == ECS_STAGE_MAGIC, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); + flecs_stage_deinit(world, &stages[i]); + } + + ecs_vector_free(world->worker_stages); + } + + if (stage_count) { + world->worker_stages = ecs_vector_new(ecs_stage_t, stage_count); + + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = ecs_vector_add( + &world->worker_stages, ecs_stage_t); + flecs_stage_init(world, stage); + stage->id = 1 + i; /* 0 is reserved for main/temp stage */ + + /* Set thread_ctx to stage, as this stage might be used in a + * multithreaded context */ + stage->thread_ctx = (ecs_world_t*)stage; + } + } else { + /* Set to NULL to prevent double frees */ + world->worker_stages = NULL; + } + + /* Regardless of whether the stage was just initialized or not, when the + * ecs_set_stages function is called, all stages inherit the auto_merge + * property from the world */ + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = world->stage.auto_merge; + } +} + +int32_t ecs_get_stage_count( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return ecs_vector_count(world->worker_stages); +} + +int32_t ecs_get_stage_id( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (world->magic == ECS_STAGE_MAGIC) { + ecs_stage_t *stage = (ecs_stage_t*)world; + + /* Index 0 is reserved for main stage */ + return stage->id - 1; + } else if (world->magic == ECS_WORLD_MAGIC) { + return 0; + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vector_count(world->worker_stages) > stage_id, + ECS_INVALID_PARAMETER, NULL); + + return (ecs_world_t*)ecs_vector_get( + world->worker_stages, ecs_stage_t, stage_id); +} + +bool ecs_staging_begin( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_defer_begin(ecs_get_stage(world, i)); + } + + bool is_readonly = world->is_readonly; + + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + world->is_readonly = true; + + return is_readonly; +} + +void ecs_staging_end( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->is_readonly == true, ECS_INVALID_OPERATION, NULL); + + /* After this it is safe again to mutate the world directly */ + world->is_readonly = false; + + do_auto_merge(world); +} + +void ecs_merge( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + do_manual_merge(world); +} + +void ecs_set_automerge( + ecs_world_t *world, + bool auto_merge) +{ + /* If a world is provided, set auto_merge globally for the world. This + * doesn't actually do anything (the main stage never merges) but it serves + * as the default for when stages are created. */ + if (world->magic == ECS_WORLD_MAGIC) { + world->stage.auto_merge = auto_merge; + + /* Propagate change to all stages */ + int i, stage_count = ecs_get_stage_count(world); + for (i = 0; i < stage_count; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + stage->auto_merge = auto_merge; + } + + /* If a stage is provided, override the auto_merge value for the individual + * stage. This allows an application to control per-stage which stage should + * be automatically merged and which one shouldn't */ + } else { + /* Magic needs to be either a world or a stage */ + ecs_assert(world->magic == ECS_STAGE_MAGIC, + ECS_INVALID_FROM_WORKER, NULL); + + ecs_stage_t *stage = (ecs_stage_t*)world; + stage->auto_merge = auto_merge; + } +} + +bool ecs_stage_is_readonly( + const ecs_world_t *stage) +{ + const ecs_world_t *world = ecs_get_world(stage); + + if (stage->magic == ECS_STAGE_MAGIC) { + if (((ecs_stage_t*)stage)->asynchronous) { + return false; + } + } + + if (world->is_readonly) { + if (stage->magic == ECS_WORLD_MAGIC) { + return true; + } + } else { + if (stage->magic == ECS_STAGE_MAGIC) { + return true; + } + } + + return false; +} + +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world) +{ + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_stage_init(world, stage); + + stage->id = -1; + stage->auto_merge = false; + stage->asynchronous = true; + + ecs_defer_begin((ecs_world_t*)stage); + + return (ecs_world_t*)stage; +} + +void ecs_async_stage_free( + ecs_world_t *world) +{ + ecs_assert(world->magic == ECS_STAGE_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_assert(stage->asynchronous == true, ECS_INVALID_PARAMETER, NULL); + flecs_stage_deinit(stage->world, stage); + ecs_os_free(stage); +} + +bool ecs_stage_is_async( + ecs_world_t *stage) +{ + if (!stage) { + return false; + } + + if (stage->magic != ECS_STAGE_MAGIC) { + return false; + } + + return ((ecs_stage_t*)stage)->asynchronous; +} + +bool ecs_is_deferred( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->defer != 0; +} diff --git a/fggl/ecs2/flecs/src/strbuf.c b/fggl/ecs2/flecs/src/strbuf.c new file mode 100644 index 0000000000000000000000000000000000000000..dea07434f0dfabe68c27f40d497c039d79623967 --- /dev/null +++ b/fggl/ecs2/flecs/src/strbuf.c @@ -0,0 +1,467 @@ +#include "private_api.h" + +/* Add an extra element to the buffer */ +static +void ecs_strbuf_grow( + ecs_strbuf_t *b) +{ + /* Allocate new element */ + ecs_strbuf_element_embedded *e = ecs_os_malloc(sizeof(ecs_strbuf_element_embedded)); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = true; + e->super.buf = e->buf; + e->super.pos = 0; + e->super.next = NULL; +} + +/* Add an extra dynamic element */ +static +void ecs_strbuf_grow_str( + ecs_strbuf_t *b, + char *str, + char *alloc_str, + int32_t size) +{ + /* Allocate new element */ + ecs_strbuf_element_str *e = ecs_os_malloc(sizeof(ecs_strbuf_element_str)); + b->size += b->current->pos; + b->current->next = (ecs_strbuf_element*)e; + b->current = (ecs_strbuf_element*)e; + b->elementCount ++; + e->super.buffer_embedded = false; + e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); + e->super.next = NULL; + e->super.buf = str; + e->alloc_str = alloc_str; +} + +static +char* ecs_strbuf_ptr( + ecs_strbuf_t *b) +{ + if (b->buf) { + return &b->buf[b->current->pos]; + } else { + return &b->current->buf[b->current->pos]; + } +} + +/* Compute the amount of space left in the current element */ +static +int32_t ecs_strbuf_memLeftInCurrentElement( + ecs_strbuf_t *b) +{ + if (b->current->buffer_embedded) { + return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; + } else { + return 0; + } +} + +/* Compute the amount of space left */ +static +int32_t ecs_strbuf_memLeft( + ecs_strbuf_t *b) +{ + if (b->max) { + return b->max - b->size - b->current->pos; + } else { + return INT_MAX; + } +} + +static +void ecs_strbuf_init( + ecs_strbuf_t *b) +{ + /* Initialize buffer structure only once */ + if (!b->elementCount) { + b->size = 0; + b->firstElement.super.next = NULL; + b->firstElement.super.pos = 0; + b->firstElement.super.buffer_embedded = true; + b->firstElement.super.buf = b->firstElement.buf; + b->elementCount ++; + b->current = (ecs_strbuf_element*)&b->firstElement; + } +} + +/* Quick custom function to copy a maxium number of characters and + * simultaneously determine length of source string. */ +static +int32_t fast_strncpy( + char * dst, + const char * src, + int n_cpy, + int n) +{ + ecs_assert(n_cpy >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(n >= 0, ECS_INTERNAL_ERROR, NULL); + + const char *ptr, *orig = src; + char ch; + + for (ptr = src; (ptr - orig < n) && (ch = *ptr); ptr ++) { + if (ptr - orig < n_cpy) { + *dst = ch; + dst ++; + } + } + + ecs_assert(ptr - orig < INT32_MAX, ECS_INTERNAL_ERROR, NULL); + + return (int32_t)(ptr - orig); +} + +/* Append a format string to a buffer */ +static +bool ecs_strbuf_vappend_intern( + ecs_strbuf_t *b, + const char* str, + va_list args) +{ + bool result = true; + va_list arg_cpy; + + if (!str) { + return result; + } + + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + + if (!memLeft) { + return false; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; + + va_copy(arg_cpy, args); + memRequired = vsnprintf( + ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + + ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); + + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* If string is a format string, a new buffer of size memRequired is + * needed to re-evaluate the format string and only use the part that + * wasn't already copied to the previous element */ + if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { + /* Resulting string fits in standard-size buffer. Note that the + * entire string needs to fit, not just the remainder, as the + * format string cannot be partially evaluated */ + ecs_strbuf_grow(b); + + /* Copy entire string to new buffer */ + ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); + + /* Ignore the part of the string that was copied into the + * previous buffer. The string copied into the new buffer could + * be memmoved so that only the remainder is left, but that is + * most likely more expensive than just keeping the entire + * string. */ + + /* Update position in buffer */ + b->current->pos += memRequired; + } else { + /* Resulting string does not fit in standard-size buffer. + * Allocate a new buffer that can hold the entire string. */ + char *dst = ecs_os_malloc(memRequired + 1); + ecs_os_vsprintf(dst, str, arg_cpy); + ecs_strbuf_grow_str(b, dst, dst, memRequired); + } + } else { + /* Buffer max has been reached */ + result = false; + } + + va_end(arg_cpy); + + return result; +} + +static +bool ecs_strbuf_append_intern( + ecs_strbuf_t *b, + const char* str, + int n) +{ + bool result = true; + + if (!str) { + return result; + } + + ecs_strbuf_init(b); + + int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); + int32_t memLeft = ecs_strbuf_memLeft(b); + + if (memLeft <= 0) { + return false; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t max_copy = b->buf ? memLeft : memLeftInElement; + int32_t memRequired; + + if (n < 0) n = INT_MAX; + + memRequired = fast_strncpy(ecs_strbuf_ptr(b), str, max_copy, n); + + if (memRequired <= memLeftInElement) { + /* Element was large enough to fit string */ + b->current->pos += memRequired; + } else if ((memRequired - memLeftInElement) < memLeft) { + /* Element was not large enough, but buffer still has space */ + b->current->pos += memLeftInElement; + memRequired -= memLeftInElement; + + /* Current element was too small, copy remainder into new element */ + if (memRequired < ECS_STRBUF_ELEMENT_SIZE) { + /* A standard-size buffer is large enough for the new string */ + ecs_strbuf_grow(b); + + /* Copy the remainder to the new buffer */ + if (n) { + /* If a max number of characters to write is set, only a + * subset of the string should be copied to the buffer */ + ecs_os_strncpy( + ecs_strbuf_ptr(b), + str + memLeftInElement, + (size_t)memRequired); + } else { + ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement); + } + + /* Update to number of characters copied to new buffer */ + b->current->pos += memRequired; + } else { + char *remainder = ecs_os_strdup(str + memLeftInElement); + ecs_strbuf_grow_str(b, remainder, remainder, memRequired); + } + } else { + /* Buffer max has been reached */ + result = false; + } + + return result; +} + +bool ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) +{ + bool result = ecs_strbuf_vappend_intern( + b, fmt, args + ); + + return result; +} + +bool ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) +{ + va_list args; + va_start(args, fmt); + bool result = ecs_strbuf_vappend_intern( + b, fmt, args + ); + va_end(args); + + return result; +} + +bool ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) +{ + return ecs_strbuf_append_intern( + b, str, len + ); +} + +bool ecs_strbuf_appendstr_zerocpy( + ecs_strbuf_t *b, + char* str) +{ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, str, str, 0); + return true; +} + +bool ecs_strbuf_appendstr_zerocpy_const( + ecs_strbuf_t *b, + const char* str) +{ + /* Removes const modifier, but logic prevents changing / delete string */ + ecs_strbuf_init(b); + ecs_strbuf_grow_str(b, (char*)str, NULL, 0); + return true; +} + +bool ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) +{ + return ecs_strbuf_append_intern( + b, str, -1 + ); +} + +bool ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer) +{ + if (src_buffer->elementCount) { + if (src_buffer->buf) { + return ecs_strbuf_appendstr(dst_buffer, src_buffer->buf); + } else { + ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; + + /* Copy first element as it is inlined in the src buffer */ + ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + + while ((e = e->next)) { + dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); + *dst_buffer->current->next = *e; + } + } + + *src_buffer = ECS_STRBUF_INIT; + } + + return true; +} + +char* ecs_strbuf_get(ecs_strbuf_t *b) { + char* result = NULL; + + if (b->elementCount) { + if (b->buf) { + b->buf[b->current->pos] = '\0'; + result = ecs_os_strdup(b->buf); + } else { + void *next = NULL; + int32_t len = b->size + b->current->pos + 1; + + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + + result = ecs_os_malloc(len); + char* ptr = result; + + do { + ecs_os_memcpy(ptr, e->buf, e->pos); + ptr += e->pos; + next = e->next; + if (e != &b->firstElement.super) { + if (!e->buffer_embedded) { + ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); + } + ecs_os_free(e); + } + } while ((e = next)); + + result[len - 1] = '\0'; + } + } else { + result = NULL; + } + + b->elementCount = 0; + + return result; +} + +void ecs_strbuf_reset(ecs_strbuf_t *b) { + if (b->elementCount && !b->buf) { + void *next = NULL; + ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + do { + next = e->next; + if (e != (ecs_strbuf_element*)&b->firstElement) { + ecs_os_free(e); + } + } while ((e = next)); + } + + *b = ECS_STRBUF_INIT; +} + +void ecs_strbuf_list_push( + ecs_strbuf_t *buffer, + const char *list_open, + const char *separator) +{ + buffer->list_sp ++; + buffer->list_stack[buffer->list_sp].count = 0; + buffer->list_stack[buffer->list_sp].separator = separator; + + if (list_open) { + ecs_strbuf_appendstr(buffer, list_open); + } +} + +void ecs_strbuf_list_pop( + ecs_strbuf_t *buffer, + const char *list_close) +{ + buffer->list_sp --; + + if (list_close) { + ecs_strbuf_appendstr(buffer, list_close); + } +} + +void ecs_strbuf_list_next( + ecs_strbuf_t *buffer) +{ + int32_t list_sp = buffer->list_sp; + if (buffer->list_stack[list_sp].count != 0) { + ecs_strbuf_appendstr(buffer, buffer->list_stack[list_sp].separator); + } + buffer->list_stack[list_sp].count ++; +} + +bool ecs_strbuf_list_append( + ecs_strbuf_t *buffer, + const char *fmt, + ...) +{ + ecs_strbuf_list_next(buffer); + + va_list args; + va_start(args, fmt); + bool result = ecs_strbuf_vappend_intern( + buffer, fmt, args + ); + va_end(args); + + return result; +} + +bool ecs_strbuf_list_appendstr( + ecs_strbuf_t *buffer, + const char *str) +{ + ecs_strbuf_list_next(buffer); + return ecs_strbuf_appendstr(buffer, str); +} diff --git a/fggl/ecs2/flecs/src/switch_list.c b/fggl/ecs2/flecs/src/switch_list.c new file mode 100644 index 0000000000000000000000000000000000000000..1823255a79ab9fa6e5305c02818e9d925be1597d --- /dev/null +++ b/fggl/ecs2/flecs/src/switch_list.c @@ -0,0 +1,359 @@ +#include "private_api.h" + +#ifdef FLECS_SANITIZE +static +void verify_nodes( + flecs_switch_header_t *hdr, + flecs_switch_node_t *nodes) +{ + if (!hdr) { + return; + } + + int32_t prev = -1, elem = hdr->element, count = 0; + while (elem != -1) { + ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); + prev = elem; + elem = nodes[elem].next; + count ++; + } + + ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); +} +#else +#define verify_nodes(hdr, nodes) +#endif + +static +flecs_switch_header_t *get_header( + const ecs_switch_t *sw, + uint64_t value) +{ + if (value == 0) { + return NULL; + } + + value = (uint32_t)value; + + ecs_assert(value >= sw->min, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value <= sw->max, ECS_INTERNAL_ERROR, NULL); + + uint64_t index = value - sw->min; + + return &sw->headers[index]; +} + +static +void remove_node( + flecs_switch_header_t *hdr, + flecs_switch_node_t *nodes, + flecs_switch_node_t *node, + int32_t element) +{ + ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); + + /* Update previous node/header */ + if (hdr->element == element) { + ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); + /* If this is the first node, update the header */ + hdr->element = node->next; + } else { + /* If this is not the first node, update the previous node to the + * removed node's next ptr */ + ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); + flecs_switch_node_t *prev_node = &nodes[node->prev]; + prev_node->next = node->next; + } + + /* Update next node */ + int32_t next = node->next; + if (next != -1) { + ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); + /* If this is not the last node, update the next node to point to the + * removed node's prev ptr */ + flecs_switch_node_t *next_node = &nodes[next]; + next_node->prev = node->prev; + } + + /* Decrease count of current header */ + hdr->count --; + ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); +} + +ecs_switch_t* flecs_switch_new( + uint64_t min, + uint64_t max, + int32_t elements) +{ + ecs_assert(min <= max, ECS_INVALID_PARAMETER, NULL); + + /* Min must be larger than 0, as 0 is an invalid entity id, and should + * therefore never occur as case id */ + ecs_assert(min > 0, ECS_INVALID_PARAMETER, NULL); + + ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); + result->min = (uint32_t)min; + result->max = (uint32_t)max; + + int32_t count = (int32_t)(max - min) + 1; + result->headers = ecs_os_calloc(ECS_SIZEOF(flecs_switch_header_t) * count); + result->nodes = ecs_vector_new(flecs_switch_node_t, elements); + result->values = ecs_vector_new(uint64_t, elements); + + int64_t i; + for (i = 0; i < count; i ++) { + result->headers[i].element = -1; + result->headers[i].count = 0; + } + + flecs_switch_node_t *nodes = ecs_vector_first( + result->nodes, flecs_switch_node_t); + uint64_t *values = ecs_vector_first( + result->values, uint64_t); + + for (i = 0; i < elements; i ++) { + nodes[i].prev = -1; + nodes[i].next = -1; + values[i] = 0; + } + + return result; +} + +void flecs_switch_free( + ecs_switch_t *sw) +{ + ecs_os_free(sw->headers); + ecs_vector_free(sw->nodes); + ecs_vector_free(sw->values); + ecs_os_free(sw); +} + +void flecs_switch_add( + ecs_switch_t *sw) +{ + flecs_switch_node_t *node = ecs_vector_add(&sw->nodes, flecs_switch_node_t); + uint64_t *value = ecs_vector_add(&sw->values, uint64_t); + node->prev = -1; + node->next = -1; + *value = 0; +} + +void flecs_switch_set_count( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count == count) { + return; + } + + ecs_vector_set_count(&sw->nodes, flecs_switch_node_t, count); + ecs_vector_set_count(&sw->values, uint64_t, count); + + flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + + int32_t i; + for (i = old_count; i < count; i ++) { + flecs_switch_node_t *node = &nodes[i]; + node->prev = -1; + node->next = -1; + values[i] = 0; + } +} + +void flecs_switch_ensure( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + if (old_count >= count) { + return; + } + + flecs_switch_set_count(sw, count); +} + +void flecs_switch_addn( + ecs_switch_t *sw, + int32_t count) +{ + int32_t old_count = ecs_vector_count(sw->nodes); + flecs_switch_set_count(sw, old_count + count); +} + +void flecs_switch_set( + ecs_switch_t *sw, + int32_t element, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t cur_value = values[element]; + + /* If the node is already assigned to the value, nothing to be done */ + if (cur_value == value) { + return; + } + + flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + flecs_switch_node_t *node = &nodes[element]; + + flecs_switch_header_t *cur_hdr = get_header(sw, cur_value); + flecs_switch_header_t *dst_hdr = get_header(sw, value); + + verify_nodes(cur_hdr, nodes); + verify_nodes(dst_hdr, nodes); + + /* If value is not 0, and dst_hdr is NULL, then this is not a valid value + * for this switch */ + ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); + + if (cur_hdr) { + remove_node(cur_hdr, nodes, node, element); + } + + /* Now update the node itself by adding it as the first node of dst */ + node->prev = -1; + values[element] = value; + + if (dst_hdr) { + node->next = dst_hdr->element; + + /* Also update the dst header */ + int32_t first = dst_hdr->element; + if (first != -1) { + ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); + flecs_switch_node_t *first_node = &nodes[first]; + first_node->prev = element; + } + + dst_hdr->element = element; + dst_hdr->count ++; + } +} + +void flecs_switch_remove( + ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + uint64_t value = values[element]; + flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); + flecs_switch_node_t *node = &nodes[element]; + + /* If node is currently assigned to a case, remove it from the list */ + if (value != 0) { + flecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); + + verify_nodes(hdr, nodes); + remove_node(hdr, nodes, node, element); + } + + int32_t last_elem = ecs_vector_count(sw->nodes) - 1; + if (last_elem != element) { + flecs_switch_node_t *last = ecs_vector_last(sw->nodes, flecs_switch_node_t); + int32_t next = last->next, prev = last->prev; + if (next != -1) { + flecs_switch_node_t *n = &nodes[next]; + n->prev = element; + } + + if (prev != -1) { + flecs_switch_node_t *n = &nodes[prev]; + n->next = element; + } else { + flecs_switch_header_t *hdr = get_header(sw, values[last_elem]); + if (hdr && hdr->element != -1) { + ecs_assert(hdr->element == last_elem, + ECS_INTERNAL_ERROR, NULL); + hdr->element = element; + } + } + } + + /* Remove element from arrays */ + ecs_vector_remove(sw->nodes, flecs_switch_node_t, element); + ecs_vector_remove(sw->values, uint64_t, element); +} + +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + uint64_t *values = ecs_vector_first(sw->values, uint64_t); + return values[element]; +} + +ecs_vector_t* flecs_switch_values( + const ecs_switch_t *sw) +{ + return sw->values; +} + +int32_t flecs_switch_case_count( + const ecs_switch_t *sw, + uint64_t value) +{ + flecs_switch_header_t *hdr = get_header(sw, value); + if (!hdr) { + return 0; + } + + return hdr->count; +} + +void flecs_switch_swap( + ecs_switch_t *sw, + int32_t elem_1, + int32_t elem_2) +{ + uint64_t v1 = flecs_switch_get(sw, elem_1); + uint64_t v2 = flecs_switch_get(sw, elem_2); + + flecs_switch_set(sw, elem_2, v1); + flecs_switch_set(sw, elem_1, v2); +} + +int32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert((uint32_t)value <= sw->max, ECS_INVALID_PARAMETER, NULL); + ecs_assert((uint32_t)value >= sw->min, ECS_INVALID_PARAMETER, NULL); + + flecs_switch_header_t *hdr = get_header(sw, value); + ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + + return hdr->element; +} + +int32_t flecs_switch_next( + const ecs_switch_t *sw, + int32_t element) +{ + ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); + ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + + flecs_switch_node_t *nodes = ecs_vector_first( + sw->nodes, flecs_switch_node_t); + + return nodes[element].next; +} diff --git a/fggl/ecs2/flecs/src/table.c b/fggl/ecs2/flecs/src/table.c new file mode 100644 index 0000000000000000000000000000000000000000..50f804f76f01dfea5c50445c4ceaa5993db3ab0b --- /dev/null +++ b/fggl/ecs2/flecs/src/table.c @@ -0,0 +1,2305 @@ +#include "private_api.h" + +ecs_data_t* flecs_init_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *result) +{ + ecs_type_t type = table->type; + int32_t i, + count = table->column_count, + sw_count = table->sw_column_count, + bs_count = table->bs_column_count; + + /* Root tables don't have columns */ + if (!count && !sw_count && !bs_count) { + result->columns = NULL; + return result; + } + + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + if (count && !sw_count) { + result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * count); + } else if (count || sw_count) { + /* If a table has switch columns, store vector with the case values + * as a regular column, so it's easier to access for systems. To + * enable this, we need to allocate more space. */ + int32_t type_count = ecs_vector_count(type); + result->columns = ecs_os_calloc(ECS_SIZEOF(ecs_column_t) * type_count); + } + + if (count) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Is the column a component? */ + const EcsComponent *component = flecs_component_from_id(world, e); + if (component) { + /* Is the component associated wit a (non-empty) type? */ + if (component->size) { + /* This is a regular component column */ + result->columns[i].size = flecs_to_i16(component->size); + result->columns[i].alignment = flecs_to_i16(component->alignment); + } else { + /* This is a tag */ + } + } else { + /* This is an entity that was added to the type */ + } + } + } + + if (sw_count) { + int32_t sw_offset = table->sw_column_offset; + result->sw_columns = ecs_os_calloc(ECS_SIZEOF(ecs_sw_column_t) * sw_count); + + for (i = 0; i < sw_count; i ++) { + ecs_entity_t e = entities[i + sw_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t sw_type = type_ptr->normalized; + + ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); + int32_t sw_array_count = ecs_vector_count(sw_type); + + ecs_switch_t *sw = flecs_switch_new( + sw_array[0], + sw_array[sw_array_count - 1], + 0); + result->sw_columns[i].data = sw; + result->sw_columns[i].type = sw_type; + } + } + + if (bs_count) { + result->bs_columns = ecs_os_calloc(ECS_SIZEOF(ecs_bs_column_t) * bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&result->bs_columns[i].data); + } + } + + return result; +} + +static +ecs_flags32_t get_component_action_flags( + const ecs_type_info_t *c_info) +{ + ecs_flags32_t flags = 0; + + if (c_info->lifecycle.ctor) { + flags |= EcsTableHasCtors; + } + if (c_info->lifecycle.dtor) { + flags |= EcsTableHasDtors; + } + if (c_info->lifecycle.copy) { + flags |= EcsTableHasCopy; + } + if (c_info->lifecycle.move) { + flags |= EcsTableHasMove; + } + + return flags; +} + +/* Check if table has instance of component, including pairs */ +static +bool has_component( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t component) +{ + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + for (i = 0; i < count; i ++) { + if (component == ecs_get_typeid(world, entities[i])) { + return true; + } + } + + return false; +} + +static +void notify_component_info( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_type_t table_type = table->type; + if (!component || has_component(world, table_type, component)){ + int32_t column_count = ecs_vector_count(table_type); + ecs_assert(!component || column_count != 0, ECS_INTERNAL_ERROR, NULL); + + if (!column_count) { + return; + } + + if (!table->c_info) { + table->c_info = ecs_os_calloc( + ECS_SIZEOF(ecs_type_info_t*) * column_count); + } + + /* Reset lifecycle flags before recomputing */ + table->flags &= ~EcsTableHasLifecycle; + + /* Recompute lifecycle flags */ + ecs_entity_t *array = ecs_vector_first(table_type, ecs_entity_t); + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_entity_t c = ecs_get_typeid(world, array[i]); + if (!c) { + continue; + } + + const ecs_type_info_t *c_info = flecs_get_c_info(world, c); + if (c_info) { + ecs_flags32_t flags = get_component_action_flags(c_info); + table->flags |= flags; + } + + /* Store pointer to c_info for fast access */ + table->c_info[i] = (ecs_type_info_t*)c_info; + } + } +} + +static +void notify_trigger( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) +{ + (void)world; + + if (!(table->flags & EcsTableIsDisabled)) { + if (event == EcsOnAdd) { + table->flags |= EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + table->flags |= EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + table->flags |= EcsTableHasOnSet; + } else if (event == EcsUnSet) { + table->flags |= EcsTableHasUnSet; + } + } +} + +static +void run_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + int32_t count = ecs_vector_count(data->entities); + if (count) { + flecs_run_monitors(world, table, table->un_set_all, 0, count, NULL); + + int32_t i, type_count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + for (i = 0; i < type_count; i ++) { + ecs_ids_t removed = { + .array = &ids[i], + .count = 1 + }; + + flecs_run_remove_actions(world, table, data, 0, count, &removed); + } + } +} + +static +int compare_matched_query( + const void *ptr1, + const void *ptr2) +{ + const ecs_matched_query_t *m1 = ptr1; + const ecs_matched_query_t *m2 = ptr2; + ecs_query_t *q1 = m1->query; + ecs_query_t *q2 = m2->query; + ecs_assert(q1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(q2 != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t s1 = q1->system; + ecs_entity_t s2 = q2->system; + ecs_assert(s1 != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(s2 != 0, ECS_INTERNAL_ERROR, NULL); + + return (s1 > s2) - (s1 < s2); +} + +static +void add_monitor( + ecs_vector_t **array, + ecs_query_t *query, + int32_t matched_table_index) +{ + /* Add the system to a list that contains all OnSet systems matched with + * this table. This makes it easy to get the list of systems that need to be + * executed when all components are set, like when new_w_data is used */ + ecs_matched_query_t *m = ecs_vector_add(array, ecs_matched_query_t); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + m->query = query; + m->matched_table_index = matched_table_index; + + /* Sort the system list so that it is easy to get the difference OnSet + * OnSet systems between two tables. */ + qsort( + ecs_vector_first(*array, ecs_matched_query_t), + flecs_to_size_t(ecs_vector_count(*array)), + ECS_SIZEOF(ecs_matched_query_t), + compare_matched_query); +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of queries that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_monitor( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + + /* First check if system is already registered as monitor. It is possible + * the query just wants to update the matched_table_index (for example, if + * query tables got reordered) */ + ecs_vector_each(table->monitors, ecs_matched_query_t, m, { + if (m->query == query) { + m->matched_table_index = matched_table_index; + return; + } + }); + + add_monitor(&table->monitors, query, matched_table_index); + +#ifndef NDEBUG + char *str = ecs_type_str(world, table->type); + ecs_trace_2("monitor #[green]%s#[reset] registered with table #[red]%s", + ecs_get_name(world, query->system), str); + ecs_os_free(str); +#endif +} + +static +bool is_override( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t comp) +{ + if (!(table->flags & EcsTableHasIsA)) { + return false; + } + + ecs_type_t type = table->type; + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_RELATION(e, EcsIsA)) { + if (ecs_has_id(world, ECS_PAIR_OBJECT(e), comp)) { + return true; + } + } + } + + return false; +} + +static +void register_on_set( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + + if (table->column_count) { + if (!table->on_set) { + table->on_set = + ecs_os_calloc(ECS_SIZEOF(ecs_vector_t) * table->column_count); + } + + /* Get the matched table which holds the list of actual components */ + ecs_matched_table_t *matched_table = ecs_vector_get( + query->tables, ecs_matched_table_t, matched_table_index); + + /* Keep track of whether query matches overrides. When a component is + * removed, diffing these arrays between the source and detination + * tables gives the list of OnSet systems to run, after exposing the + * component that was overridden. */ + bool match_override = false; + + /* Add system to each matched column. This makes it easy to get the list + * of systems when setting a single component. */ + ecs_term_t *terms = query->filter.terms; + int32_t i, count = query->filter.term_count; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_id_t *subj = &term->args[0]; + ecs_oper_kind_t oper = term->oper; + + if (!(subj->set.mask & EcsSelf) || !subj->entity || + subj->entity != EcsThis) + { + continue; + } + + if (oper != EcsAnd && oper != EcsOptional) { + continue; + } + + ecs_entity_t comp = matched_table->ids[i]; + int32_t index = ecs_type_index_of(table->type, 0, comp); + if (index == -1) { + continue; + } + + if (index >= table->column_count) { + continue; + } + + ecs_vector_t *set_c = table->on_set[index]; + ecs_matched_query_t *m = ecs_vector_add(&set_c, ecs_matched_query_t); + m->query = query; + m->matched_table_index = matched_table_index; + table->on_set[index] = set_c; + + match_override |= is_override(world, table, comp); + } + + if (match_override) { + add_monitor(&table->on_set_override, query, matched_table_index); + } + } + + add_monitor(&table->on_set_all, query, matched_table_index); +} + +static +void register_un_set( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + (void)world; + table->flags |= EcsTableHasUnSet; + add_monitor(&table->un_set_all, query, matched_table_index); +} + +/* -- Private functions -- */ + +/* If table goes from 0 to >0 entities or from >0 entities to 0 entities notify + * queries. This allows systems associated with queries to move inactive tables + * out of the main loop. */ +static +void table_activate( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + bool activate) +{ + if (query) { + flecs_query_notify(world, query, &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } else { + ecs_vector_t *queries = table->queries; + ecs_query_t **buffer = ecs_vector_first(queries, ecs_query_t*); + int32_t i, count = ecs_vector_count(queries); + + for (i = 0; i < count; i ++) { + flecs_query_notify(world, buffer[i], &(ecs_query_event_t) { + .kind = activate ? EcsQueryTableNonEmpty : EcsQueryTableEmpty, + .table = table + }); + } + } +} + +/* This function is called when a query is matched with a table. A table keeps + * a list of tables that match so that they can be notified when the table + * becomes empty / non-empty. */ +static +void register_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query, + int32_t matched_table_index) +{ + /* Register system with the table */ + if (!(query->flags & EcsQueryNoActivation)) { +#ifndef NDEBUG + /* Sanity check if query has already been added */ + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + ecs_assert(*q != query, ECS_INTERNAL_ERROR, NULL); + } +#endif + + ecs_query_t **q = ecs_vector_add(&table->queries, ecs_query_t*); + if (q) *q = query; + + ecs_data_t *data = flecs_table_get_data(table); + if (data && ecs_vector_count(data->entities)) { + table_activate(world, table, query, true); + } + } + + /* Register the query as a monitor */ + if (query->flags & EcsQueryMonitor) { + table->flags |= EcsTableHasMonitors; + register_monitor(world, table, query, matched_table_index); + } + + /* Register the query as an on_set system */ + if (query->flags & EcsQueryOnSet) { + register_on_set(world, table, query, matched_table_index); + } + + /* Register the query as an un_set system */ + if (query->flags & EcsQueryUnSet) { + register_un_set(world, table, query, matched_table_index); + } +} + +/* This function is called when a query is unmatched with a table. This can + * happen for queries that have shared components expressions in their signature + * and those shared components changed (for example, a base removed a comp). */ +static +void unregister_query( + ecs_world_t *world, + ecs_table_t *table, + ecs_query_t *query) +{ + (void)world; + + if (!(query->flags & EcsQueryNoActivation)) { + int32_t i, count = ecs_vector_count(table->queries); + for (i = 0; i < count; i ++) { + ecs_query_t **q = ecs_vector_get(table->queries, ecs_query_t*, i); + if (*q == query) { + break; + } + } + + /* Query must have been registered with table */ + ecs_assert(i != count, ECS_INTERNAL_ERROR, NULL); + + /* Remove query */ + ecs_vector_remove(table->queries, ecs_query_t*, i); + } +} + +ecs_data_t* flecs_table_get_data( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->data; +} + +ecs_data_t* flecs_table_get_or_create_data( + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_data_t *data = table->data; + if (!data) { + data = table->data = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); + } + return data; +} + +static +void ctor_component( + ecs_world_t *world, + ecs_type_info_t * cdata, + ecs_column_t * column, + ecs_entity_t * entities, + int32_t row, + int32_t count) +{ + /* A new component is constructed */ + ecs_xtor_t ctor; + if (cdata && (ctor = cdata->lifecycle.ctor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; + + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + + ctor(world, cdata->component, entities, ptr, + flecs_to_size_t(size), count, ctx); + } +} + +static +void dtor_component( + ecs_world_t *world, + ecs_type_info_t * cdata, + ecs_column_t * column, + ecs_entity_t * entities, + int32_t row, + int32_t count) +{ + if (!count) { + return; + } + + /* An old component is destructed */ + ecs_xtor_t dtor; + if (cdata && (dtor = cdata->lifecycle.dtor)) { + void *ctx = cdata->lifecycle.ctx; + int16_t size = column->size; + int16_t alignment = column->alignment; + + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + void *ptr = ecs_vector_get_t(column->data, size, alignment, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + dtor(world, cdata->component, &entities[row], ptr, + flecs_to_size_t(size), count, ctx); + } +} + +static +void dtor_all_components( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t row, + int32_t count, + bool update_entity_index, + bool is_delete) +{ + /* Can't delete and not update the entity index */ + ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t **records = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + int32_t i, c, column_count = table->column_count, end = row + count; + + (void)records; + + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Prevent the storage from getting modified while deleting */ + ecs_defer_begin(world); + + /* Throw up a lock just to be sure */ + table->lock = true; + + /* Iterate entities first, then components. This ensures that only one + * entity is invalidated at a time, which ensures that destructors can + * safely access other entities. */ + for (i = row; i < end; i ++) { + for (c = 0; c < column_count; c++) { + ecs_column_t *column = &data->columns[c]; + dtor_component(world, table->c_info[c], column, entities, i, 1); + } + + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + if (update_entity_index) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + + if (is_delete) { + ecs_eis_delete(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } else { + // If this is not a delete, clear the entity index record + ecs_record_t r = {NULL, 0}; + ecs_eis_set(world, e, &r); + } + } else { + /* This should only happen in rare cases, such as when the data + * cleaned up is not part of the world (like with snapshots) */ + } + } + + table->lock = false; + + ecs_defer_end(world); + + /* If table does not have destructors, just update entity index */ + } else if (update_entity_index) { + if (is_delete) { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + + ecs_eis_delete(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } else { + for (i = row; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i] == ecs_eis_get(world, e), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!e || records[i]->table == table, + ECS_INTERNAL_ERROR, NULL); + ecs_record_t r = {NULL, 0}; + ecs_eis_set(world, e, &r); + } + } + } +} + +static +void fini_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + bool do_on_remove, + bool update_entity_index, + bool is_delete, + bool deactivate) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (!data) { + return; + } + + if (do_on_remove) { + run_on_remove(world, table, data); + } + + int32_t count = flecs_table_data_count(data); + if (count) { + dtor_all_components(world, table, data, 0, count, + update_entity_index, is_delete); + } + + /* Sanity check */ + ecs_assert(ecs_vector_count(data->record_ptrs) == + ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + + 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 || (ecs_vector_count(columns[c].data) == + ecs_vector_count(data->entities)), ECS_INTERNAL_ERROR, NULL); + + ecs_vector_free(columns[c].data); + } + ecs_os_free(columns); + data->columns = NULL; + } + + ecs_sw_column_t *sw_columns = data->sw_columns; + if (sw_columns) { + int32_t c, column_count = table->sw_column_count; + for (c = 0; c < column_count; c ++) { + flecs_switch_free(sw_columns[c].data); + } + ecs_os_free(sw_columns); + data->sw_columns = NULL; + } + + ecs_bs_column_t *bs_columns = data->bs_columns; + if (bs_columns) { + int32_t c, column_count = table->bs_column_count; + for (c = 0; c < column_count; c ++) { + flecs_bitset_deinit(&bs_columns[c].data); + } + ecs_os_free(bs_columns); + data->bs_columns = NULL; + } + + ecs_vector_free(data->entities); + ecs_vector_free(data->record_ptrs); + + data->entities = NULL; + data->record_ptrs = NULL; + + if (deactivate && count) { + table_activate(world, table, 0, false); + } +} + +/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ +void flecs_table_clear_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data) +{ + fini_data(world, table, data, false, false, false, false); +} + +/* Cleanup, no OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, flecs_table_get_data(table), + false, true, false, true); +} + +/* Cleanup, run OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + fini_data(world, table, flecs_table_get_data(table), true, 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) +{ + fini_data(world, table, flecs_table_get_data(table), true, true, true, true); +} + +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all UnSet handlers, if any */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + ecs_data_t *data = flecs_table_get_data(table); + if (data) { + run_on_remove(world, table, data); + } +} + +/* Free table resources. */ +void flecs_table_free( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + (void)world; + + /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ + ecs_data_t *data = flecs_table_get_data(table); + fini_data(world, table, data, false, true, true, false); + + flecs_table_clear_edges(world, table); + + flecs_unregister_table(world, table); + + ecs_os_free(table->lo_edges); + ecs_map_free(table->hi_edges); + ecs_vector_free(table->queries); + ecs_os_free(table->dirty_state); + ecs_vector_free(table->monitors); + ecs_vector_free(table->on_set_all); + ecs_vector_free(table->on_set_override); + ecs_vector_free(table->un_set_all); + + if (table->c_info) { + ecs_os_free(table->c_info); + } + + if (table->on_set) { + int32_t i; + for (i = 0; i < table->column_count; i ++) { + ecs_vector_free(table->on_set[i]); + } + ecs_os_free(table->on_set); + } + + table->id = 0; + + ecs_os_free(table->data); +} + +/* Free table type. Do this separately from freeing the table as types can be + * in use by application destructors. */ +void flecs_table_free_type( + ecs_table_t *table) +{ + ecs_vector_free((ecs_vector_t*)table->type); +} + +/* 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, NULL); + + (void)world; + ecs_os_free(table->lo_edges); + ecs_map_free(table->hi_edges); + table->lo_edges = NULL; + table->hi_edges = NULL; +} + +static +void mark_table_dirty( + ecs_table_t *table, + int32_t index) +{ + if (table->dirty_state) { + table->dirty_state[index] ++; + } +} + +void flecs_table_mark_dirty( + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (table->dirty_state) { + int32_t index = ecs_type_index_of(table->type, 0, component); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + table->dirty_state[index] ++; + } +} + +static +void move_switch_columns( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->sw_column_count; + int32_t i_new = 0, new_column_count = new_table->sw_column_count; + + if (!old_column_count || !new_column_count) { + return; + } + + ecs_sw_column_t *old_columns = old_data->sw_columns; + ecs_sw_column_t *new_columns = new_data->sw_columns; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t offset_new = new_table->sw_column_offset; + int32_t offset_old = old_table->sw_column_offset; + + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; + + if (new_component == old_component) { + ecs_switch_t *old_switch = old_columns[i_old].data; + ecs_switch_t *new_switch = new_columns[i_new].data; + + flecs_switch_ensure(new_switch, new_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_switch_get(old_switch, old_index + i); + flecs_switch_set(new_switch, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +static +void move_bitset_columns( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index, + int32_t count) +{ + int32_t i_old = 0, old_column_count = old_table->bs_column_count; + int32_t i_new = 0, new_column_count = new_table->bs_column_count; + + if (!old_column_count || !new_column_count) { + return; + } + + ecs_bs_column_t *old_columns = old_data->bs_columns; + ecs_bs_column_t *new_columns = new_data->bs_columns; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t offset_new = new_table->bs_column_offset; + int32_t offset_old = old_table->bs_column_offset; + + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new + offset_new]; + ecs_entity_t old_component = old_components[i_old + offset_old]; + + if (new_component == old_component) { + ecs_bitset_t *old_bs = &old_columns[i_old].data; + ecs_bitset_t *new_bs = &new_columns[i_new].data; + + flecs_bitset_ensure(new_bs, new_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(old_bs, old_index + i); + flecs_bitset_set(new_bs, new_index + i, value); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +static +void ensure_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t * column_count_out, + int32_t * sw_column_count_out, + int32_t * bs_column_count_out, + ecs_column_t ** columns_out, + ecs_sw_column_t ** sw_columns_out, + ecs_bs_column_t ** bs_columns_out) +{ + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + + /* It is possible that the table data was created without content. + * Now that data is going to be written to the table, initialize */ + if (column_count | sw_column_count | bs_column_count) { + columns = data->columns; + sw_columns = data->sw_columns; + bs_columns = data->bs_columns; + + if (!columns && !sw_columns && !bs_columns) { + flecs_init_data(world, table, data); + columns = data->columns; + sw_columns = data->sw_columns; + bs_columns = data->bs_columns; + + ecs_assert(sw_column_count == 0 || sw_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(bs_column_count == 0 || bs_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + *column_count_out = column_count; + *sw_column_count_out = sw_column_count; + *bs_column_count_out = bs_column_count; + *columns_out = columns; + *sw_columns_out = sw_columns; + *bs_columns_out = bs_columns; + } + + ecs_assert(!column_count || columns, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!sw_column_count || sw_columns, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!bs_column_count || bs_columns, ECS_INTERNAL_ERROR, NULL); +} + +static +void grow_column( + ecs_world_t *world, + ecs_entity_t * entities, + ecs_column_t * column, + ecs_type_info_t * c_info, + int32_t to_add, + int32_t new_size, + bool construct) +{ + ecs_vector_t *vec = column->data; + int16_t alignment = column->alignment; + + int32_t size = column->size; + int32_t count = ecs_vector_count(vec); + int32_t old_size = ecs_vector_size(vec); + int32_t new_count = count + to_add; + bool can_realloc = new_size != old_size; + + ecs_assert(new_size >= new_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; + if (c_info && count && can_realloc && (move = c_info->lifecycle.move)) { + ecs_xtor_t ctor = c_info->lifecycle.ctor; + ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Create new vector */ + ecs_vector_t *new_vec = ecs_vector_new_t(size, alignment, new_size); + ecs_vector_set_count_t(&new_vec, size, alignment, new_count); + + void *old_buffer = ecs_vector_first_t( + vec, size, alignment); + + void *new_buffer = ecs_vector_first_t( + new_vec, size, alignment); + + /* First construct elements (old and new) in new buffer */ + ctor(world, c_info->component, entities, new_buffer, + flecs_to_size_t(size), construct ? new_count : count, + c_info->lifecycle.ctx); + + /* Move old elements */ + move(world, c_info->component, entities, entities, + new_buffer, old_buffer, flecs_to_size_t(size), count, + c_info->lifecycle.ctx); + + /* Free old vector */ + ecs_vector_free(vec); + column->data = new_vec; + } else { + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_vector_set_size_t(&vec, size, alignment, new_size); + } + + void *elem = ecs_vector_addn_t(&vec, size, alignment, to_add); + + ecs_xtor_t ctor; + if (construct && c_info && (ctor = c_info->lifecycle.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(world, c_info->component, &entities[count], elem, + flecs_to_size_t(size), to_add, c_info->lifecycle.ctx); + } + + column->data = vec; + } + + ecs_assert(ecs_vector_size(column->data) == new_size, + ECS_INTERNAL_ERROR, NULL); +} + +static +int32_t grow_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur_count = flecs_table_data_count(data); + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + + /* Add record to record ptr array */ + ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); + ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_vector_size(data->record_ptrs) > size) { + size = ecs_vector_size(data->record_ptrs); + } + + /* Add entity to column with entity ids */ + ecs_vector_set_size(&data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); + + /* Initialize entity ids and record ptrs */ + int32_t i; + if (ids) { + for (i = 0; i < to_add; i ++) { + e[i] = ids[i]; + } + } else { + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } + ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); + + /* Add elements to each column array */ + ecs_type_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + if (!column->size) { + continue; + } + + ecs_type_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; + } + + grow_column(world, entities, column, c_info, to_add, size, true); + ecs_assert(ecs_vector_size(columns[i].data) == size, + ECS_INTERNAL_ERROR, NULL); + } + + /* Add elements to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_switch_t *sw = sw_columns[i].data; + flecs_switch_addn(sw, to_add); + } + + /* Add elements to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i].data; + flecs_bitset_addn(bs, to_add); + } + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + if (!world->is_readonly && !cur_count) { + table_activate(world, table, 0, true); + } + + table->alloc_count ++; + + /* Return index of first added entity */ + return cur_count; +} + +static +void fast_append( + ecs_column_t *columns, + int32_t column_count) +{ + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_add_t(&column->data, size, alignment); + } + } +} + +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + ecs_entity_t entity, + ecs_record_t * record, + bool construct) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + int32_t count = ecs_vector_count(data->entities); + int32_t size = ecs_vector_size(data->entities); + + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns = NULL; + ecs_sw_column_t *sw_columns = NULL; + ecs_bs_column_t *bs_columns = NULL; + + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *e = entity; + + /* Keep track of alloc count. This allows references to check if cached + * pointers need to be updated. */ + table->alloc_count += (count == size); + + /* Add record ptr to array with record ptrs */ + ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + *r = record; + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + /* 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 (!world->is_readonly && !count) { + table_activate(world, table, 0, true); + } + + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + fast_append(columns, column_count); + return count; + } + + ecs_type_info_t **c_info_array = table->c_info; + ecs_entity_t *entities = ecs_vector_first( + data->entities, ecs_entity_t); + + /* Reobtain size to ensure that the columns have the same size as the + * entities and record vectors. This keeps reasoning about when allocations + * occur easier. */ + size = ecs_vector_size(data->entities); + + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + if (!column->size) { + continue; + } + + ecs_type_info_t *c_info = NULL; + if (c_info_array) { + c_info = c_info_array[i]; + } + + grow_column(world, entities, column, c_info, 1, size, construct); + + ecs_assert( + ecs_vector_size(columns[i].data) == ecs_vector_size(data->entities), + ECS_INTERNAL_ERROR, NULL); + + ecs_assert( + ecs_vector_count(columns[i].data) == ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); + } + + /* Add element to each switch column */ + for (i = 0; i < sw_column_count; i ++) { + ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_switch_t *sw = sw_columns[i].data; + flecs_switch_add(sw); + columns[i + table->sw_column_offset].data = flecs_switch_values(sw); + } + + /* Add element to each bitset column */ + for (i = 0; i < bs_column_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i].data; + flecs_bitset_addn(bs, 1); + } + + return count; +} + +static +void fast_delete_last( + ecs_column_t *columns, + int32_t column_count) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vector_remove_last(column->data); + } +} + +static +void 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]; + int16_t size = column->size; + if (size) { + int16_t alignment = column->alignment; + ecs_vector_remove_t(column->data, size, alignment, index); + } + } +} + +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t index, + bool destruct) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_vector_t *v_entities = data->entities; + int32_t count = ecs_vector_count(v_entities); + + 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 = ecs_vector_first(v_entities, ecs_entity_t); + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[index]; + entities[index] = entity_to_move; + ecs_vector_remove_last(v_entities); + + /* Move last record ptr to index */ + ecs_vector_t *v_records = data->record_ptrs; + ecs_assert(count < ecs_vector_count(v_records), ECS_INTERNAL_ERROR, NULL); + + ecs_record_t **records = ecs_vector_first(v_records, ecs_record_t*); + ecs_record_t *record_to_move = records[count]; + records[index] = record_to_move; + ecs_vector_remove_last(v_records); + + /* Update record of moved entity in entity index */ + if (index != count) { + if (record_to_move) { + if (record_to_move->row >= 0) { + record_to_move->row = index + 1; + } else { + record_to_move->row = -(index + 1); + } + 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 */ + mark_table_dirty(table, 0); + + /* If table is empty, deactivate it */ + if (!count) { + table_activate(world, table, NULL, false); + } + + /* Destruct component data */ + ecs_type_info_t **c_info_array = table->c_info; + 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) { + fast_delete_last(columns, column_count); + } else { + fast_delete(columns, column_count, index); + } + + return; + } + + /* Last element, destruct & remove */ + if (index == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + ecs_type_info_t *c_info = c_info_array[i]; + ecs_xtor_t dtor; + if (c_info && (dtor = c_info->lifecycle.dtor)) { + ecs_size_t size = c_info->size; + ecs_size_t alignment = c_info->alignment; + dtor(world, c_info->component, &entity_to_delete, + ecs_vector_last_t(columns[i].data, size, alignment), + flecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } + } + } + + fast_delete_last(columns, column_count); + + /* Not last element, move last element to deleted element & destruct */ + } else { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { + ecs_assert(c_info_array != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_size_t size = column->size; + ecs_size_t align = column->alignment; + ecs_vector_t *vec = column->data; + void *dst = ecs_vector_get_t(vec, size, align, index); + void *src = ecs_vector_last_t(vec, size, align); + + ecs_type_info_t *c_info = c_info_array[i]; + ecs_move_ctor_t move_dtor; + if (c_info && (move_dtor = c_info->lifecycle.move_dtor)) { + move_dtor(world, c_info->component, &c_info->lifecycle, + &entity_to_move, &entity_to_delete, dst, src, + flecs_to_size_t(size), 1, c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + + ecs_vector_remove_last(vec); + } + + } else { + fast_delete(columns, column_count, index); + } + } + + /* Remove elements from switch columns */ + ecs_sw_column_t *sw_columns = data->sw_columns; + int32_t sw_column_count = table->sw_column_count; + for (i = 0; i < sw_column_count; i ++) { + flecs_switch_remove(sw_columns[i].data, index); + } + + /* Remove elements from bitset columns */ + ecs_bs_column_t *bs_columns = data->bs_columns; + int32_t bs_column_count = table->bs_column_count; + for (i = 0; i < bs_column_count; i ++) { + flecs_bitset_remove(&bs_columns[i].data, index); + } +} + +static +void fast_move( + ecs_table_t * new_table, + ecs_data_t * new_data, + int32_t new_index, + ecs_table_t * old_table, + ecs_data_t * old_data, + int32_t old_index) +{ + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t i_new = 0, new_column_count = new_table->column_count; + int32_t i_old = 0, old_column_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + + if (size) { + int16_t alignment = new_column->alignment; + void *dst = ecs_vector_get_t( + new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t( + old_column->data, size, alignment, old_index); + + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(dst, src, size); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } +} + +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + ecs_data_t *new_data, + int32_t new_index, + ecs_table_t *old_table, + ecs_data_t *old_data, + int32_t old_index, + bool construct) +{ + ecs_assert(new_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); + + ecs_assert(old_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(old_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new_data != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!((new_table->flags | old_table->flags) & EcsTableIsComplex)) { + fast_move(new_table, new_data, new_index, old_table, old_data, old_index); + return; + } + + move_switch_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); + + move_bitset_columns( + new_table, new_data, new_index, old_table, old_data, old_index, 1); + + bool same_entity = dst_entity == src_entity; + + ecs_type_t new_type = new_table->type; + ecs_type_t old_type = old_table->type; + + int32_t i_new = 0, new_column_count = new_table->column_count; + int32_t i_old = 0, old_column_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + for (; (i_new < new_column_count) && (i_old < old_column_count);) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + + if (new_component == old_component) { + ecs_column_t *new_column = &new_columns[i_new]; + ecs_column_t *old_column = &old_columns[i_old]; + int16_t size = new_column->size; + int16_t alignment = new_column->alignment; + + if (size) { + void *dst = ecs_vector_get_t( + new_column->data, size, alignment, new_index); + void *src = ecs_vector_get_t( + old_column->data, size, alignment, old_index); + + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_info_t *cdata = new_table->c_info[i_new]; + if (same_entity) { + ecs_move_ctor_t callback; + if (cdata && (callback = cdata->lifecycle.ctor_move_dtor)) { + void *ctx = cdata->lifecycle.ctx; + /* ctor + move + dtor */ + callback(world, new_component, &cdata->lifecycle, + &dst_entity, &src_entity, + dst, src, flecs_to_size_t(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + ecs_copy_ctor_t copy; + if (cdata && (copy = cdata->lifecycle.copy_ctor)) { + void *ctx = cdata->lifecycle.ctx; + copy(world, new_component, &cdata->lifecycle, + &dst_entity, &src_entity, + dst, src, flecs_to_size_t(size), 1, ctx); + } else { + ecs_os_memcpy(dst, src, size); + } + } + } + } else { + if (new_component < old_component) { + if (construct) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } + } else { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } + } + + i_new += new_component <= old_component; + i_old += new_component >= old_component; + } + + if (construct) { + for (; (i_new < new_column_count); i_new ++) { + ctor_component(world, new_table->c_info[i_new], + &new_columns[i_new], &dst_entity, new_index, 1); + } + } + + for (; (i_old < old_column_count); i_old ++) { + dtor_component(world, old_table->c_info[i_old], + &old_columns[i_old], &src_entity, old_index, 1); + } +} + +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t to_add, + const ecs_entity_t *ids) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + int32_t cur_count = flecs_table_data_count(data); + return grow_data(world, table, data, to_add, cur_count + to_add, ids); +} + +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data, + int32_t size) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + int32_t cur_count = flecs_table_data_count(data); + + if (cur_count < size) { + grow_data(world, table, data, 0, size, NULL); + } else if (!size) { + /* Initialize columns if 0 is passed. This is a shortcut to initialize + * columns when, for example, an API call is inserting bulk data. */ + int32_t column_count = table->column_count; + int32_t sw_column_count = table->sw_column_count; + int32_t bs_column_count = table->bs_column_count; + ecs_column_t *columns; + ecs_sw_column_t *sw_columns; + ecs_bs_column_t *bs_columns; + ensure_data(world, table, data, &column_count, &sw_column_count, + &bs_column_count, &columns, &sw_columns, &bs_columns); + } +} + +int32_t flecs_table_data_count( + const ecs_data_t *data) +{ + return data ? ecs_vector_count(data->entities) : 0; +} + +static +void swap_switch_columns( + ecs_table_t *table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->sw_column_count; + if (!column_count) { + return; + } + + ecs_sw_column_t *columns = data->sw_columns; + + for (i = 0; i < column_count; i ++) { + ecs_switch_t *sw = columns[i].data; + flecs_switch_swap(sw, row_1, row_2); + } +} + +static +void swap_bitset_columns( + ecs_table_t *table, + ecs_data_t * data, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->bs_column_count; + if (!column_count) { + return; + } + + ecs_bs_column_t *columns = data->bs_columns; + + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i].data; + flecs_bitset_swap(bs, row_1, row_2); + } +} + +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t row_1, + int32_t row_2) +{ + (void)world; + + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); + + if (row_1 == row_2) { + return; + } + + /* If the table is monitored indicate that there has been a change */ + mark_table_dirty(table, 0); + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; + + ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); + ecs_record_t *record_ptr_1 = record_ptrs[row_1]; + ecs_record_t *record_ptr_2 = record_ptrs[row_2]; + + 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 */ + bool watched_1 = record_ptr_1->row < 0; + bool watched_2 = record_ptr_2->row < 0; + + /* Swap entities & records */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = flecs_row_to_record(row_2, watched_1); + record_ptr_2->row = flecs_row_to_record(row_1, watched_2); + record_ptrs[row_1] = record_ptr_2; + record_ptrs[row_2] = record_ptr_1; + + swap_switch_columns(table, data, row_1, row_2); + swap_bitset_columns(table, data, row_1, row_2); + + ecs_column_t *columns = data->columns; + if (!columns) { + return; + } + + /* Swap columns */ + int32_t i, column_count = table->column_count; + + for (i = 0; i < column_count; i ++) { + int16_t size = columns[i].size; + int16_t alignment = columns[i].alignment; + void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); + + if (size) { + void *tmp = ecs_os_alloca(size); + + void *el_1 = ECS_OFFSET(ptr, size * row_1); + void *el_2 = ECS_OFFSET(ptr, size * row_2); + + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } + } +} + +static +void merge_vector( + ecs_vector_t **dst_out, + ecs_vector_t *src, + int16_t size, + int16_t alignment) +{ + ecs_vector_t *dst = *dst_out; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); + } + + *dst_out = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); + + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); + + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + + ecs_vector_free(src); + *dst_out = dst; + } +} + +static +void merge_column( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t column_id, + ecs_vector_t *src) +{ + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_type_info_t *c_info = table->c_info[column_id]; + ecs_column_t *column = &data->columns[column_id]; + ecs_vector_t *dst = column->data; + int16_t size = column->size; + int16_t alignment = column->alignment; + int32_t dst_count = ecs_vector_count(dst); + + if (!dst_count) { + if (dst) { + ecs_vector_free(dst); + } + + column->data = src; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = ecs_vector_count(src); + ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); + column->data = dst; + + /* Construct new values */ + if (c_info) { + ctor_component( + world, c_info, column, entities, dst_count, src_count); + } + + void *dst_ptr = ecs_vector_first_t(dst, size, alignment); + void *src_ptr = ecs_vector_first_t(src, size, alignment); + + dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); + + /* Move values into column */ + ecs_move_t move; + if (c_info && (move = c_info->lifecycle.move)) { + move(world, c_info->component, entities, entities, + dst_ptr, src_ptr, flecs_to_size_t(size), src_count, + c_info->lifecycle.ctx); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + } + + ecs_vector_free(src); + } +} + +static +void merge_table_data( + ecs_world_t *world, + ecs_table_t * new_table, + ecs_table_t * old_table, + int32_t old_count, + int32_t new_count, + ecs_data_t * old_data, + ecs_data_t * new_data) +{ + int32_t i_new = 0, new_component_count = new_table->column_count; + int32_t i_old = 0, old_component_count = old_table->column_count; + ecs_entity_t *new_components = ecs_vector_first(new_table->type, ecs_entity_t); + ecs_entity_t *old_components = ecs_vector_first(old_table->type, ecs_entity_t); + + ecs_column_t *old_columns = old_data->columns; + ecs_column_t *new_columns = new_data->columns; + + if (!new_columns && !new_data->entities) { + flecs_init_data(world, new_table, new_data); + new_columns = new_data->columns; + } + + ecs_assert(!new_component_count || new_columns, ECS_INTERNAL_ERROR, NULL); + + if (!old_count) { + return; + } + + /* Merge entities */ + merge_vector(&new_data->entities, old_data->entities, ECS_SIZEOF(ecs_entity_t), + ECS_ALIGNOF(ecs_entity_t)); + old_data->entities = NULL; + ecs_entity_t *entities = ecs_vector_first(new_data->entities, ecs_entity_t); + + ecs_assert(ecs_vector_count(new_data->entities) == old_count + new_count, + ECS_INTERNAL_ERROR, NULL); + + /* Merge entity index record pointers */ + merge_vector(&new_data->record_ptrs, old_data->record_ptrs, + ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); + old_data->record_ptrs = NULL; + + for (; (i_new < new_component_count) && (i_old < old_component_count); ) { + ecs_entity_t new_component = new_components[i_new]; + ecs_entity_t old_component = old_components[i_old]; + int16_t size = new_columns[i_new].size; + int16_t alignment = new_columns[i_new].alignment; + + if (new_component == old_component) { + merge_column(world, new_table, new_data, i_new, + old_columns[i_old].data); + old_columns[i_old].data = NULL; + + /* Mark component column as dirty */ + mark_table_dirty(new_table, i_new + 1); + + i_new ++; + i_old ++; + } else if (new_component < old_component) { + /* New column does not occur in old table, make sure vector is large + * enough. */ + if (size) { + ecs_column_t *column = &new_columns[i_new]; + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_type_info_t *c_info = new_table->c_info[i_new]; + if (c_info) { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + } + + i_new ++; + } else if (new_component > old_component) { + if (size) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_type_info_t *c_info = old_table->c_info[i_old]; + if (c_info) { + dtor_component(world, c_info, column, + entities, 0, old_count); + } + + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + } + + i_old ++; + } + } + + move_switch_columns( + new_table, new_data, new_count, old_table, old_data, 0, old_count); + + /* Initialize remaining columns */ + for (; i_new < new_component_count; i_new ++) { + ecs_column_t *column = &new_columns[i_new]; + int16_t size = column->size; + int16_t alignment = column->alignment; + + if (size) { + ecs_vector_set_count_t(&column->data, size, alignment, + old_count + new_count); + + /* Construct new values */ + ecs_type_info_t *c_info = new_table->c_info[i_new]; + if (c_info) { + ctor_component(world, c_info, column, + entities, 0, old_count + new_count); + } + } + } + + /* Destroy remaining columns */ + for (; i_old < old_component_count; i_old ++) { + ecs_column_t *column = &old_columns[i_old]; + + /* Destruct old values */ + ecs_type_info_t *c_info = old_table->c_info[i_old]; + if (c_info) { + dtor_component(world, c_info, column, entities, + 0, old_count); + } + + /* Old column does not occur in new table, remove */ + ecs_vector_free(column->data); + column->data = NULL; + } + + /* Mark entity column as dirty */ + mark_table_dirty(new_table, 0); +} + +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_data_t *data = table->data; + if (!data) { + return 0; + } + + return flecs_table_data_count(data); +} + +ecs_data_t* flecs_table_merge( + ecs_world_t *world, + ecs_table_t *new_table, + ecs_table_t *old_table, + ecs_data_t *new_data, + ecs_data_t *old_data) +{ + ecs_assert(old_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!old_table->lock, ECS_LOCKED_STORAGE, NULL); + + bool move_data = false; + + /* If there is nothing to merge to, just clear the old table */ + if (!new_table) { + flecs_table_clear_data(world, old_table, old_data); + return NULL; + } else { + ecs_assert(!new_table->lock, ECS_LOCKED_STORAGE, NULL); + } + + /* If there is no data to merge, drop out */ + if (!old_data) { + return NULL; + } + + if (!new_data) { + new_data = flecs_table_get_or_create_data(new_table); + if (new_table == old_table) { + move_data = true; + } + } + + ecs_entity_t *old_entities = ecs_vector_first(old_data->entities, ecs_entity_t); + int32_t old_count = ecs_vector_count(old_data->entities); + int32_t new_count = ecs_vector_count(new_data->entities); + + ecs_record_t **old_records = ecs_vector_first( + old_data->record_ptrs, ecs_record_t*); + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < old_count; i ++) { + ecs_record_t *record; + if (new_table != old_table) { + record = old_records[i]; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + record = ecs_eis_ensure(world, old_entities[i]); + } + + bool is_monitored = record->row < 0; + record->row = flecs_row_to_record(new_count + i, is_monitored); + record->table = new_table; + } + + /* Merge table columns */ + if (move_data) { + *new_data = *old_data; + } else { + merge_table_data(world, new_table, old_table, old_count, new_count, + old_data, new_data); + } + + new_table->alloc_count ++; + + if (!new_count && old_count) { + table_activate(world, new_table, NULL, true); + } + + return new_data; +} + +void flecs_table_replace_data( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t * data) +{ + int32_t prev_count = 0; + ecs_data_t *table_data = table->data; + ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (table_data) { + prev_count = ecs_vector_count(table_data->entities); + run_on_remove(world, table, table_data); + flecs_table_clear_data(world, table, table_data); + } + + if (data) { + table_data = flecs_table_get_or_create_data(table); + *table_data = *data; + } else { + return; + } + + int32_t count = ecs_table_count(table); + + if (!prev_count && count) { + table_activate(world, table, 0, true); + } else if (prev_count && !count) { + table_activate(world, table, 0, false); + } +} + +bool flecs_table_match_filter( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_filter_t * filter) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!filter) { + return true; + } + + ecs_type_t type = table->type; + + if (filter->include) { + /* If filter kind is exact, types must be the same */ + if (filter->include_kind == EcsMatchExact) { + if (type != filter->include) { + return false; + } + + /* Default for include_kind is MatchAll */ + } else if (!flecs_type_contains(world, type, filter->include, + filter->include_kind != EcsMatchAny, true)) + { + return false; + } + } + + if (filter->exclude) { + /* If filter kind is exact, types must be the same */ + if (filter->exclude_kind == EcsMatchExact) { + if (type == filter->exclude) { + return false; + } + + /* Default for exclude_kind is MatchAny */ + } else if (flecs_type_contains(world, type, filter->exclude, + filter->exclude_kind == EcsMatchAll, true)) + { + return false; + } + } + + return true; +} + +int32_t* flecs_table_get_dirty_state( + ecs_table_t *table) +{ + ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); + + if (!table->dirty_state) { + table->dirty_state = ecs_os_calloc(ECS_SIZEOF(int32_t) * (table->column_count + 1)); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + } + return table->dirty_state; +} + +int32_t* flecs_table_get_monitor( + ecs_table_t *table) +{ + int32_t *dirty_state = flecs_table_get_dirty_state(table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t column_count = table->column_count; + return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); +} + +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t * event) +{ + if (world->is_fini) { + return; + } + + switch(event->kind) { + case EcsTableQueryMatch: + register_query( + world, table, event->query, event->matched_table_index); + break; + case EcsTableQueryUnmatch: + unregister_query( + world, table, event->query); + break; + case EcsTableComponentInfo: + notify_component_info(world, table, event->component); + break; + case EcsTableTriggerMatch: + notify_trigger(world, table, event->event); + break; + } +} + +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (world->magic == ECS_WORLD_MAGIC && !world->is_readonly) { + table->lock ++; + } +} + +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (world->magic == ECS_WORLD_MAGIC && !world->is_readonly) { + table->lock --; + ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); + } +} + +bool ecs_table_has_module( + ecs_table_t *table) +{ + return table->flags & EcsTableHasModule; +} + +ecs_column_t *ecs_table_column_for_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + if (tr) { + ecs_data_t *data = table->data; + if (data) { + return &data->columns[tr->column]; + } + } + + return NULL; +} diff --git a/fggl/ecs2/flecs/src/table_graph.c b/fggl/ecs2/flecs/src/table_graph.c new file mode 100644 index 0000000000000000000000000000000000000000..8d10e3417ec4a9a42ec046b79a71314558d207f7 --- /dev/null +++ b/fggl/ecs2/flecs/src/table_graph.c @@ -0,0 +1,1012 @@ +#include "private_api.h" + +static +uint64_t ids_hash(const void *ptr) { + const ecs_ids_t *type = ptr; + ecs_id_t *ids = type->array; + int32_t count = type->count; + uint64_t hash = flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); + return hash; +} + +static +int ids_compare(const void *ptr_1, const void *ptr_2) { + const ecs_ids_t *type_1 = ptr_1; + const ecs_ids_t *type_2 = ptr_2; + + 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; + + int32_t i; + for (i = 0; i < count_1; i ++) { + ecs_id_t id_1 = ids_1[i]; + ecs_id_t id_2 = ids_2[i]; + + if (id_1 != id_2) { + return (id_1 > id_2) - (id_1 < id_2); + } + } + + return 0; +} + +ecs_hashmap_t flecs_table_hashmap_new(void) { + return flecs_hashmap_new(ecs_ids_t, ecs_table_t*, ids_hash, ids_compare); +} + +const EcsComponent* flecs_component_from_id( + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_entity_t pair = 0; + + /* If this is a pair, get the pair component from the identifier */ + if (ECS_HAS_ROLE(e, PAIR)) { + pair = e; + e = ecs_get_alive(world, ECS_PAIR_RELATION(e)); + + if (ecs_has_id(world, e, EcsTag)) { + return NULL; + } + } + + if (e & ECS_ROLE_MASK) { + return NULL; + } + + const EcsComponent *component = ecs_get(world, e, EcsComponent); + if ((!component || !component->size) && pair) { + /* If this is a pair column and the pair is not a component, use + * the component type of the component the pair is applied to. */ + e = ECS_PAIR_OBJECT(pair); + + /* Because generations are not stored in the pair, get the currently + * alive id */ + e = ecs_get_alive(world, e); + + /* If a pair is used with a not alive id, the pair is not valid */ + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + + component = ecs_get(world, e, EcsComponent); + } + + return component; +} + +/* Count number of columns with data (excluding tags) */ +static +int32_t data_column_count( + ecs_world_t * world, + ecs_table_t * table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + /* Typically all components will be clustered together at the start of + * the type as components are created from a separate id pool, and type + * vectors are sorted. + * Explicitly check for EcsComponent and EcsName since the ecs_has check + * doesn't work during bootstrap. */ + if ((component == ecs_id(EcsComponent)) || + (component == ecs_pair(ecs_id(EcsIdentifier), EcsName)) || + (component == ecs_pair(ecs_id(EcsIdentifier), EcsSymbol)) || + flecs_component_from_id(world, component) != NULL) + { + count = c_ptr_i + 1; + } + }); + + return count; +} + +/* Ensure the ids used in the columns exist */ +static +int32_t ensure_columns( + ecs_world_t * world, + ecs_table_t * table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, PAIR)) { + ecs_entity_t rel = ECS_PAIR_RELATION(component); + ecs_entity_t obj = ECS_PAIR_OBJECT(component); + ecs_ensure(world, rel); + ecs_ensure(world, obj); + } else if (component & ECS_ROLE_MASK) { + ecs_entity_t e = ECS_PAIR_OBJECT(component); + ecs_ensure(world, e); + } else { + ecs_ensure(world, component); + } + }); + + return count; +} + +/* Count number of switch columns */ +static +int32_t switch_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, SWITCH)) { + if (!count) { + table->sw_column_offset = c_ptr_i; + } + count ++; + } + }); + + return count; +} + +/* Count number of bitset columns */ +static +int32_t bitset_column_count( + ecs_table_t *table) +{ + int32_t count = 0; + ecs_vector_each(table->type, ecs_entity_t, c_ptr, { + ecs_entity_t component = *c_ptr; + + if (ECS_HAS_ROLE(component, DISABLED)) { + if (!count) { + table->bs_column_offset = c_ptr_i; + } + count ++; + } + }); + + return count; +} + +static +ecs_type_t entities_to_type( + ecs_ids_t *entities) +{ + if (entities->count) { + ecs_vector_t *result = NULL; + ecs_vector_set_count(&result, ecs_entity_t, entities->count); + ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); + ecs_os_memcpy(array, entities->array, ECS_SIZEOF(ecs_entity_t) * entities->count); + return result; + } else { + return NULL; + } +} + +static +ecs_edge_t* get_edge( + ecs_table_t *node, + ecs_entity_t e) +{ + if (e < ECS_HI_COMPONENT_ID) { + if (!node->lo_edges) { + node->lo_edges = ecs_os_calloc_n(ecs_edge_t, ECS_HI_COMPONENT_ID); + } + return &node->lo_edges[e]; + } else { + if (!node->hi_edges) { + node->hi_edges = ecs_map_new(ecs_edge_t, 1); + } + return ecs_map_ensure(node->hi_edges, ecs_edge_t, e); + } +} + +static +void init_edges( + ecs_table_t * table) +{ + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t count = ecs_vector_count(table->type); + + table->lo_edges = NULL; + table->hi_edges = NULL; + + /* Iterate components for table, initialize edges that point to self */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + ecs_edge_t *edge = get_edge(table, id); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + edge->add = table; + } +} + +static +void init_flags( + ecs_world_t * world, + ecs_table_t * table) +{ + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + int32_t count = ecs_vector_count(table->type); + + /* Iterate components to initialize table flags */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + /* As we're iterating over the table components, also set the table + * flags. These allow us to quickly determine if the table contains + * data that needs to be handled in a special way, like prefabs or + * containers */ + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; + } + + if (id == EcsModule) { + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } + + if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + table->flags |= EcsTableIsDisabled; + } + + /* If table contains disabled entities, mark it as disabled */ + if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } + + /* Does table have exclusive or columns */ + if (ECS_HAS_ROLE(id, XOR)) { + table->flags |= EcsTableHasXor; + } + + /* Does table have IsA relations */ + if (ECS_HAS_RELATION(id, EcsIsA)) { + table->flags |= EcsTableHasIsA; + } + + /* Does table have switch columns */ + if (ECS_HAS_ROLE(id, SWITCH)) { + table->flags |= EcsTableHasSwitch; + } + + /* Does table support component disabling */ + if (ECS_HAS_ROLE(id, DISABLED)) { + table->flags |= EcsTableHasDisabled; + } + + /* Does table have ChildOf relations */ + if (ECS_HAS_RELATION(id, EcsChildOf)) { + ecs_entity_t obj = ecs_pair_object(world, id); + 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; + } + } + } +} + +static +void init_table( + ecs_world_t * world, + ecs_table_t * table, + ecs_ids_t * entities) +{ + table->type = entities_to_type(entities); + table->c_info = NULL; + table->data = NULL; + table->flags = 0; + table->dirty_state = NULL; + table->monitors = NULL; + table->on_set = NULL; + table->on_set_all = NULL; + table->on_set_override = NULL; + table->un_set_all = NULL; + table->alloc_count = 0; + table->lock = 0; + + /* Ensure the component ids for the table exist */ + ensure_columns(world, table); + + table->queries = NULL; + table->column_count = data_column_count(world, table); + table->sw_column_count = switch_column_count(table); + table->bs_column_count = bitset_column_count(table); + + init_edges(table); + init_flags(world, table); + + flecs_register_table(world, table); + + /* Register component info flags for all columns */ + flecs_table_notify(world, table, &(ecs_table_event_t){ + .kind = EcsTableComponentInfo + }); +} + +static +ecs_table_t *create_table( + ecs_world_t * world, + ecs_ids_t * entities, + flecs_hashmap_result_t table_elem) +{ + ecs_table_t *result = flecs_sparse_add(world->store.tables, ecs_table_t); + result->id = flecs_sparse_last_id(world->store.tables); + + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + init_table(world, result, entities); + +#ifndef NDEBUG + char *expr = ecs_type_str(world, result->type); + ecs_trace_2("table #[green][%s]#[normal] created [%p]", expr, result); + ecs_os_free(expr); +#endif + ecs_log_push(); + + /* 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_ids_t key = { + .array = ecs_vector_first(result->type, ecs_id_t), + .count = ecs_vector_count(result->type) + }; + *(ecs_ids_t*)table_elem.key = key; + + flecs_notify_queries(world, &(ecs_query_event_t) { + .kind = EcsQueryTableMatch, + .table = result + }); + + ecs_log_pop(); + + return result; +} + +static +void add_entity_to_type( + ecs_type_t type, + ecs_entity_t add, + ecs_entity_t replace, + ecs_ids_t *out) +{ + int32_t count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + bool added = false; + + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (e == replace) { + continue; + } + + if (e > add && !added) { + out->array[el ++] = add; + added = true; + } + + out->array[el ++] = e; + + ecs_assert(el <= out->count, ECS_INTERNAL_ERROR, NULL); + } + + if (!added) { + out->array[el ++] = add; + } + + out->count = el; + + ecs_assert(out->count != 0, ECS_INTERNAL_ERROR, NULL); +} + +static +void remove_entity_from_type( + ecs_type_t type, + ecs_entity_t remove, + ecs_ids_t *out) +{ + int32_t count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + int32_t i, el = 0; + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i]; + if (e != remove) { + out->array[el ++] = e; + ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); + } + } + + out->count = el; +} + +static +void create_backlink_after_add( + ecs_table_t * next, + ecs_table_t * prev, + ecs_entity_t add) +{ + ecs_edge_t *edge = get_edge(next, add); + if (!edge->remove) { + edge->remove = prev; + } +} + +static +void create_backlink_after_remove( + ecs_table_t * next, + ecs_table_t * prev, + ecs_entity_t add) +{ + ecs_edge_t *edge = get_edge(next, add); + if (!edge->add) { + edge->add = prev; + } +} + +static +ecs_entity_t find_xor_replace( + ecs_world_t * world, + ecs_table_t * table, + ecs_type_t type, + ecs_entity_t add) +{ + if (table->flags & EcsTableHasXor) { + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + int32_t i, type_count = ecs_vector_count(type); + ecs_type_t xor_type = NULL; + + for (i = type_count - 1; i >= 0; i --) { + ecs_entity_t e = array[i]; + if (ECS_HAS_ROLE(e, XOR)) { + ecs_entity_t e_type = e & ECS_COMPONENT_MASK; + const EcsType *type_ptr = ecs_get(world, e_type, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_has_id( + world, type_ptr->normalized, add, true)) + { + xor_type = type_ptr->normalized; + } + } else if (xor_type) { + if (ecs_type_has_id(world, xor_type, e, true)) { + return e; + } + } + } + } + + return 0; +} + +int32_t flecs_table_switch_from_case( + const ecs_world_t * world, + const ecs_table_t * table, + ecs_entity_t add) +{ + ecs_type_t type = table->type; + ecs_data_t *data = flecs_table_get_data(table); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + + int32_t i, count = table->sw_column_count; + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + add = add & ECS_COMPONENT_MASK; + + ecs_sw_column_t *sw_columns = NULL; + + if (data && (sw_columns = data->sw_columns)) { + /* Fast path, we can get the switch type from the column data */ + for (i = 0; i < count; i ++) { + ecs_type_t sw_type = sw_columns[i].type; + if (ecs_type_has_id(world, sw_type, add, true)) { + return i; + } + } + } else { + /* Slow path, table is empty, so we'll have to get the switch types by + * actually inspecting the switch type entities. */ + for (i = 0; i < count; i ++) { + ecs_entity_t e = array[i + table->sw_column_offset]; + ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); + e = e & ECS_COMPONENT_MASK; + + const EcsType *type_ptr = ecs_get(world, e, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_type_has_id( + world, type_ptr->normalized, add, true)) + { + return i; + } + } + } + + /* If a table was not found, this is an invalid switch case */ + ecs_abort(ECS_TYPE_INVALID_CASE, NULL); + + return -1; +} + +static +ecs_table_t *find_or_create_table_include( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t add) +{ + /* If table has one or more switches and this is a case, return self */ + if (ECS_HAS_ROLE(add, CASE)) { + ecs_assert((node->flags & EcsTableHasSwitch) != 0, + ECS_TYPE_INVALID_CASE, NULL); + return node; + } else { + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + + ecs_ids_t entities = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * (count + 1)), + .count = count + 1 + }; + + /* If table has a XOR column, check if the entity that is being added to + * the table is part of the XOR type, and if it is, find the current + * entity in the table type matching the XOR type. This entity must be + * replaced in the new table, to ensure the XOR constraint isn't + * violated. */ + ecs_entity_t replace = find_xor_replace(world, node, type, add); + + add_entity_to_type(type, add, replace, &entities); + + ecs_table_t *result = flecs_table_find_or_create(world, &entities); + + if (result != node) { + create_backlink_after_add(result, node, add); + } + + return result; + } +} + +static +ecs_table_t *find_or_create_table_exclude( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t remove) +{ + ecs_type_t type = node->type; + int32_t count = ecs_vector_count(type); + + ecs_ids_t entities = { + .array = ecs_os_alloca(ECS_SIZEOF(ecs_entity_t) * count), + .count = count + }; + + remove_entity_from_type(type, remove, &entities); + + ecs_table_t *result = flecs_table_find_or_create(world, &entities); + if (!result) { + return NULL; + } + + if (result != node) { + create_backlink_after_remove(result, node, remove); + } + + return result; +} + +ecs_table_t* flecs_table_traverse_remove( + ecs_world_t * world, + ecs_table_t * node, + ecs_ids_t * to_remove, + ecs_ids_t * removed) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = to_remove->count; + ecs_entity_t *entities = to_remove->array; + node = node ? node : &world->store.root; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Removing 0 from an entity is not valid */ + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_edge_t *edge = get_edge(node, e); + ecs_table_t *next = edge->remove; + + if (!next) { + if (edge->add == node) { + /* Find table with all components of node except 'e' */ + next = find_or_create_table_exclude(world, node, e); + if (!next) { + return NULL; + } + + edge->remove = next; + } else { + /* If the add edge does not point to self, the table + * does not have the entity in to_remove. */ + continue; + } + } + + bool has_case = ECS_HAS_ROLE(e, CASE); + if (removed && (node != next || has_case)) { + removed->array[removed->count ++] = e; + } + + node = next; + } + + return node; +} + +static +void find_owned_components( + ecs_world_t * world, + ecs_table_t * node, + ecs_entity_t base, + ecs_ids_t * owned) +{ + /* If we're adding an IsA relationship, check if the base + * has OWNED components that need to be added to the instance */ + ecs_type_t t = ecs_get_type(world, base); + + int i, count = ecs_vector_count(t); + ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + if (ECS_HAS_RELATION(e, EcsIsA)) { + find_owned_components(world, node, ECS_PAIR_OBJECT(e), owned); + } else + if (ECS_HAS_ROLE(e, OWNED)) { + e = e & ECS_COMPONENT_MASK; + + /* If entity is a type, add each component in the type */ + const EcsType *t_ptr = ecs_get(world, e, EcsType); + if (t_ptr) { + ecs_type_t n = t_ptr->normalized; + int32_t j, n_count = ecs_vector_count(n); + ecs_entity_t *n_entities = ecs_vector_first(n, ecs_entity_t); + for (j = 0; j < n_count; j ++) { + owned->array[owned->count ++] = n_entities[j]; + } + } else { + owned->array[owned->count ++] = ECS_PAIR_OBJECT(e); + } + } + } +} + +ecs_table_t* flecs_table_traverse_add( + ecs_world_t * world, + ecs_table_t * node, + ecs_ids_t * to_add, + ecs_ids_t * added) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = to_add->count; + ecs_entity_t *entities = to_add->array; + node = node ? node : &world->store.root; + + ecs_entity_t owned_array[ECS_MAX_ADD_REMOVE]; + ecs_ids_t owned = { + .array = owned_array, + .count = 0 + }; + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + + /* Adding 0 to an entity is not valid */ + ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_edge_t *edge = get_edge(node, e); + ecs_table_t *next = edge->add; + + if (!next) { + next = find_or_create_table_include(world, node, e); + ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); + edge->add = next; + } + + bool has_case = ECS_HAS_ROLE(e, CASE); + if (added && (node != next || has_case)) { + added->array[added->count ++] = e; + } + + if ((node != next) && ECS_HAS_RELATION(e, EcsIsA)) { + find_owned_components( + world, next, ecs_pair_object(world, e), &owned); + } + + node = next; + } + + /* In case OWNED components were found, add them as well */ + if (owned.count) { + node = flecs_table_traverse_add(world, node, &owned, added); + } + + return node; +} + +static +bool ecs_entity_array_is_ordered( + const ecs_ids_t *entities) +{ + ecs_entity_t prev = 0; + ecs_entity_t *array = entities->array; + int32_t i, count = entities->count; + + for (i = 0; i < count; i ++) { + if (!array[i] && !prev) { + continue; + } + if (array[i] <= prev) { + return false; + } + prev = array[i]; + } + + return true; +} + +static +int32_t ecs_entity_array_dedup( + ecs_entity_t *array, + int32_t count) +{ + int32_t j, k; + ecs_entity_t prev = array[0]; + + for (k = j = 1; k < count; j ++, k++) { + ecs_entity_t e = array[k]; + if (e == prev) { + k ++; + } + + array[j] = e; + prev = e; + } + + return count - (k - j); +} + +#ifndef NDEBUG + +static +int32_t count_occurrences( + ecs_world_t * world, + ecs_ids_t * entities, + ecs_entity_t entity, + int32_t constraint_index) +{ + const EcsType *type_ptr = ecs_get(world, entity, EcsType); + ecs_assert(type_ptr != NULL, + ECS_INVALID_PARAMETER, "flag must be applied to type"); + + ecs_type_t type = type_ptr->normalized; + int32_t count = 0; + + int i; + for (i = 0; i < constraint_index; i ++) { + ecs_entity_t e = entities->array[i]; + if (e & ECS_ROLE_MASK) { + break; + } + + if (ecs_type_has_id(world, type, e, false)) { + count ++; + } + } + + return count; +} + +static +void verify_constraints( + ecs_world_t * world, + ecs_ids_t * entities) +{ + int i, count = entities->count; + for (i = count - 1; i >= 0; i --) { + ecs_entity_t e = entities->array[i]; + ecs_entity_t mask = e & ECS_ROLE_MASK; + if (!mask || + ((mask != ECS_OR) && + (mask != ECS_XOR) && + (mask != ECS_NOT))) + { + break; + } + + ecs_entity_t entity = e & ECS_COMPONENT_MASK; + int32_t matches = count_occurrences(world, entities, entity, i); + switch(mask) { + case ECS_OR: + ecs_assert(matches >= 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + case ECS_XOR: + ecs_assert(matches == 1, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + case ECS_NOT: + ecs_assert(matches == 0, ECS_TYPE_CONSTRAINT_VIOLATION, NULL); + break; + } + } +} + +#endif + +static +ecs_table_t* find_or_create( + ecs_world_t *world, + const ecs_ids_t *ids) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + /* Make sure array is ordered and does not contain duplicates */ + int32_t type_count = ids->count; + ecs_id_t *ordered = NULL; + + if (!type_count) { + return &world->store.root; + } + + if (!ecs_entity_array_is_ordered(ids)) { + ecs_size_t size = ECS_SIZEOF(ecs_entity_t) * type_count; + ordered = ecs_os_alloca(size); + ecs_os_memcpy(ordered, ids->array, size); + qsort(ordered, (size_t)type_count, sizeof(ecs_entity_t), + flecs_entity_compare_qsort); + type_count = ecs_entity_array_dedup(ordered, type_count); + } else { + ordered = ids->array; + } + + ecs_ids_t ordered_ids = { + .array = ordered, + .count = type_count + }; + + ecs_table_t *table; + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + world->store.table_map, &ordered_ids, ecs_table_t*); + if ((table = *(ecs_table_t**)elem.value)) { + 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->is_readonly, ECS_INTERNAL_ERROR, NULL); + +#ifndef NDEBUG + /* Check for constraint violations */ + verify_constraints(world, &ordered_ids); +#endif + + /* If we get here, the table has not been found, so create it. */ + ecs_table_t *result = create_table(world, &ordered_ids, elem); + + ecs_assert(ordered_ids.count == ecs_vector_count(result->type), + ECS_INTERNAL_ERROR, NULL); + + return result; +} + +ecs_table_t* flecs_table_find_or_create( + ecs_world_t * world, + const ecs_ids_t * components) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + return find_or_create(world, components); +} + +ecs_table_t* ecs_table_from_type( + ecs_world_t *world, + ecs_type_t type) +{ + ecs_ids_t components = flecs_type_to_ids(type); + return flecs_table_find_or_create( + world, &components); +} + +void flecs_init_root_table( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_ids_t entities = { + .array = NULL, + .count = 0 + }; + + init_table(world, &world->store.root, &entities); +} + +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + uint32_t i; + + if (table->lo_edges) { + for (i = 0; i < ECS_HI_COMPONENT_ID; i ++) { + ecs_edge_t *e = &table->lo_edges[i]; + ecs_table_t *add = e->add, *remove = e->remove; + + if (add) { + add->lo_edges[i].remove = NULL; + } + if (remove) { + remove->lo_edges[i].add = NULL; + } + } + } + + ecs_map_iter_t it = ecs_map_iter(table->hi_edges); + ecs_edge_t *edge; + ecs_map_key_t component; + while ((edge = ecs_map_next(&it, ecs_edge_t, &component))) { + ecs_table_t *add = edge->add, *remove = edge->remove; + if (add) { + ecs_edge_t *e = get_edge(add, component); + e->remove = NULL; + if (!e->add) { + ecs_map_remove(add->hi_edges, component); + } + } + if (remove) { + ecs_edge_t *e = get_edge(remove, component); + e->add = NULL; + if (!e->remove) { + ecs_map_remove(remove->hi_edges, component); + } + } + } +} + +/* 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_ids_t arr = { .array = &id, .count = 1 }; + return flecs_table_traverse_add(world, table, &arr, NULL); +} + +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_ids_t arr = { .array = &id, .count = 1 }; + return flecs_table_traverse_remove(world, table, &arr, NULL); +} diff --git a/fggl/ecs2/flecs/src/trigger.c b/fggl/ecs2/flecs/src/trigger.c new file mode 100644 index 0000000000000000000000000000000000000000..64603318d95ffd8071d26d4a4c73dae63ebad8e3 --- /dev/null +++ b/fggl/ecs2/flecs/src/trigger.c @@ -0,0 +1,430 @@ +#include "private_api.h" + +static +int32_t count_events( + const ecs_entity_t *events) +{ + int32_t i; + + for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { + if (!events[i]) { + break; + } + } + + return i; +} + +static +void register_id_trigger( + ecs_map_t *set, + ecs_trigger_t *trigger) +{ + ecs_trigger_t **t = ecs_map_ensure(set, ecs_trigger_t*, trigger->id); + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + *t = trigger; +} + +static +ecs_map_t* unregister_id_trigger( + ecs_map_t *set, + ecs_trigger_t *trigger) +{ + ecs_map_remove(set, trigger->id); + + if (!ecs_map_count(set)) { + ecs_map_free(set); + return NULL; + } + + return set; +} + +static +void register_trigger( + ecs_world_t *world, + ecs_trigger_t *trigger) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *triggers = world->id_triggers; + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_trigger_t *idt = ecs_map_ensure(triggers, + ecs_id_trigger_t, trigger->term.id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int i; + for (i = 0; i < trigger->event_count; i ++) { + ecs_map_t **set = NULL; + if (trigger->events[i] == EcsOnAdd) { + set = &idt->on_add_triggers; + } else if (trigger->events[i] == EcsOnRemove) { + set = &idt->on_remove_triggers; + } else if (trigger->events[i] == EcsOnSet) { + set = &idt->on_set_triggers; + } else if (trigger->events[i] == EcsUnSet) { + set = &idt->un_set_triggers; + } else { + /* Invalid event provided */ + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + + ecs_assert(set != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!*set) { + *set = ecs_map_new(ecs_trigger_t*, 1); + + // First trigger of its kind, send table notification + flecs_notify_tables(world, trigger->term.id, &(ecs_table_event_t){ + .kind = EcsTableTriggerMatch, + .event = trigger->events[i] + }); + } + + register_id_trigger(*set, trigger); + } +} + +static +void unregister_trigger( + ecs_world_t *world, + ecs_trigger_t *trigger) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *triggers = world->id_triggers; + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_trigger_t *idt = ecs_map_get( + triggers, ecs_id_trigger_t, trigger->term.id); + if (!idt) { + return; + } + + int i; + for (i = 0; i < trigger->event_count; i ++) { + ecs_map_t **set = NULL; + if (trigger->events[i] == EcsOnAdd) { + set = &idt->on_add_triggers; + } else if (trigger->events[i] == EcsOnRemove) { + set = &idt->on_remove_triggers; + } else if (trigger->events[i] == EcsOnSet) { + set = &idt->on_set_triggers; + } else if (trigger->events[i] == EcsUnSet) { + set = &idt->un_set_triggers; + } else { + /* Invalid event provided */ + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } + if (!*set) { + return; + } + + *set = unregister_id_trigger(*set, trigger); + } + + ecs_map_remove(triggers, trigger->id); +} + +ecs_map_t* flecs_triggers_get( + const ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *triggers = world->id_triggers; + ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_trigger_t *idt = ecs_map_get(triggers, ecs_id_trigger_t, id); + if (!idt) { + return NULL; + } + + ecs_map_t *set = NULL; + + if (event == EcsOnAdd) { + set = idt->on_add_triggers; + } else if (event == EcsOnRemove) { + set = idt->on_remove_triggers; + } else if (event == EcsOnSet) { + set = idt->on_set_triggers; + } else if (event == EcsUnSet) { + set = idt->un_set_triggers; + } + + if (ecs_map_count(set)) { + return set; + } else { + return NULL; + } +} + +static +void notify_trigger_set( + ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t event, + const ecs_map_t *triggers, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count) +{ + if (!triggers) { + return; + } + + ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row < ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vector_count(data->entities), + ECS_INTERNAL_ERROR, NULL); + entities = ECS_OFFSET(entities, ECS_SIZEOF(ecs_entity_t) * row); + + int32_t index = ecs_type_index_of(table->type, 0, id); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + index ++; + + ecs_entity_t ids[1] = { id }; + int32_t columns[1] = { index }; + ecs_size_t sizes[1] = { 0 }; + + /* If there is no data, ensure that system won't try to get it */ + if (table->column_count < index) { + columns[0] = 0; + } else { + ecs_column_t *column = &data->columns[index - 1]; + if (!column->size) { + columns[0] = 0; + } + } + + void *ptr = NULL; + + if (columns[0] && data && data->columns) { + ecs_column_t *col = &data->columns[index - 1]; + ptr = ecs_vector_get_t(col->data, col->size, col->alignment, row); + sizes[0] = col->size; + } + + ecs_type_t types[1] = { ecs_type_from_id(world, id) }; + + ecs_iter_t it = { + .world = world, + .event = event, + .event_id = id, + .table = table, + .columns = columns, + .ids = ids, + .types = types, + .sizes = sizes, + .ptrs = &ptr, + .table_count = 1, + .inactive_table_count = 0, + .column_count = 1, + .table_columns = data->columns, + .entities = entities, + .offset = row, + .count = count, + .is_valid = true + }; + + ecs_map_iter_t mit = ecs_map_iter(triggers); + ecs_trigger_t *t; + while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { + it.system = t->entity; + it.self = t->self; + it.ctx = t->ctx; + it.binding_ctx = t->binding_ctx; + it.term_index = t->term.index; + t->action(&it); + } +} + +void flecs_triggers_notify( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t event, + ecs_table_t *table, + ecs_data_t *data, + int32_t row, + int32_t count) +{ + notify_trigger_set(world, id, event, + flecs_triggers_get(world, id, event), + table, data, row, count); + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t pred = ECS_PAIR_RELATION(id); + ecs_entity_t obj = ECS_PAIR_OBJECT(id); + + notify_trigger_set(world, id, event, + flecs_triggers_get(world, ecs_pair(pred, EcsWildcard), event), + table, data, row, count); + + notify_trigger_set(world, id, event, + flecs_triggers_get(world, ecs_pair(EcsWildcard, obj), event), + table, data, row, count); + + notify_trigger_set(world, id, event, + flecs_triggers_get(world, ecs_pair(EcsWildcard, EcsWildcard), event), + table, data, row, count); + } else { + notify_trigger_set(world, id, event, + flecs_triggers_get(world, EcsWildcard, event), + table, data, row, count); + } +} + +ecs_entity_t ecs_trigger_init( + ecs_world_t *world, + const ecs_trigger_desc_t *desc) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + char *name = NULL; + const char *expr = desc->expr; + + /* If entity is provided, create it */ + ecs_entity_t existing = desc->entity.entity; + ecs_entity_t entity = ecs_entity_init(world, &desc->entity); + + bool added = false; + EcsTrigger *comp = ecs_get_mut(world, entity, EcsTrigger, &added); + if (added) { + ecs_assert(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Something went wrong with the construction of the entity */ + ecs_assert(entity != 0, ECS_INVALID_PARAMETER, NULL); + name = ecs_get_fullpath(world, entity); + + ecs_term_t term; + if (expr) { + #ifdef FLECS_PARSER + const char *ptr = ecs_parse_term(world, name, expr, expr, &term); + if (!ptr) { + goto error; + } + + if (!ecs_term_is_initialized(&term)) { + ecs_parser_error( + name, expr, 0, "invalid empty trigger expression"); + goto error; + } + + if (ptr[0]) { + ecs_parser_error(name, expr, 0, + "too many terms in trigger expression (expected 1)"); + goto error; + } + #else + ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); + #endif + } else { + term = ecs_term_copy(&desc->term); + } + + if (ecs_term_finalize(world, name, expr, &term)) { + goto error; + } + + /* Currently triggers are not supported for specific entities */ + ecs_assert(term.args[0].entity == EcsThis, ECS_UNSUPPORTED, NULL); + + ecs_trigger_t *trigger = flecs_sparse_add(world->triggers, ecs_trigger_t); + trigger->id = flecs_sparse_last_id(world->triggers); + trigger->term = ecs_term_move(&term); + trigger->action = desc->callback; + trigger->ctx = desc->ctx; + trigger->binding_ctx = desc->binding_ctx; + trigger->ctx_free = desc->ctx_free; + trigger->binding_ctx_free = desc->binding_ctx_free; + trigger->event_count = count_events(desc->events); + ecs_os_memcpy(trigger->events, desc->events, + trigger->event_count * ECS_SIZEOF(ecs_entity_t)); + trigger->entity = entity; + trigger->self = desc->self; + + comp->trigger = trigger; + + /* Trigger must have at least one event */ + ecs_assert(trigger->event_count != 0, ECS_INVALID_PARAMETER, NULL); + + register_trigger(world, trigger); + + ecs_term_fini(&term); + } else { + ecs_assert(comp->trigger != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If existing entity handle was provided, override existing params */ + if (existing) { + if (desc->callback) { + ((ecs_trigger_t*)comp->trigger)->action = desc->callback; + } + if (desc->ctx) { + ((ecs_trigger_t*)comp->trigger)->ctx = desc->ctx; + } + if (desc->binding_ctx) { + ((ecs_trigger_t*)comp->trigger)->binding_ctx = desc->binding_ctx; + } + } + } + + ecs_os_free(name); + return entity; +error: + ecs_os_free(name); + return 0; +} + +void* ecs_get_trigger_ctx( + const ecs_world_t *world, + ecs_entity_t trigger) +{ + const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); + if (t) { + return t->trigger->ctx; + } else { + return NULL; + } +} + +void* ecs_get_trigger_binding_ctx( + const ecs_world_t *world, + ecs_entity_t trigger) +{ + const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); + if (t) { + return t->trigger->binding_ctx; + } else { + return NULL; + } +} + +void flecs_trigger_fini( + ecs_world_t *world, + ecs_trigger_t *trigger) +{ + unregister_trigger(world, trigger); + ecs_term_fini(&trigger->term); + + if (trigger->ctx_free) { + trigger->ctx_free(trigger->ctx); + } + + if (trigger->binding_ctx_free) { + trigger->binding_ctx_free(trigger->binding_ctx); + } + + flecs_sparse_remove(world->triggers, trigger->id); +} diff --git a/fggl/ecs2/flecs/src/type.c b/fggl/ecs2/flecs/src/type.c new file mode 100644 index 0000000000000000000000000000000000000000..93d9114d5a6980600094802e79e9048f3eec1f91 --- /dev/null +++ b/fggl/ecs2/flecs/src/type.c @@ -0,0 +1,397 @@ +#include "private_api.h" + +/* -- Private functions -- */ + +/* O(n) algorithm to check whether type 1 is equal or superset of type 2 */ +ecs_entity_t flecs_type_contains( + const ecs_world_t *world, + ecs_type_t type_1, + ecs_type_t type_2, + bool match_all, + bool match_prefab) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); + + if (!type_1) { + return 0; + } + + ecs_assert(type_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + if (type_1 == type_2) { + return *(ecs_vector_first(type_1, ecs_entity_t)); + } + + if (ecs_vector_count(type_2) == 1) { + return ecs_type_has_id(world, type_1, + ecs_vector_first(type_2, ecs_id_t)[0], !match_prefab); + } + + int32_t i_2, i_1 = 0; + ecs_entity_t e1 = 0; + ecs_entity_t *t1_array = ecs_vector_first(type_1, ecs_entity_t); + ecs_entity_t *t2_array = ecs_vector_first(type_2, ecs_entity_t); + + ecs_assert(t1_array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t2_array != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t t1_count = ecs_vector_count(type_1); + int32_t t2_count = ecs_vector_count(type_2); + + for (i_2 = 0; i_2 < t2_count; i_2 ++) { + ecs_entity_t e2 = t2_array[i_2]; + + if (i_1 >= t1_count) { + return 0; + } + + e1 = t1_array[i_1]; + + if (e2 > e1) { + do { + i_1 ++; + if (i_1 >= t1_count) { + return 0; + } + e1 = t1_array[i_1]; + } while (e2 > e1); + } + + if (e1 != e2) { + if (e1 != e2) { + if (match_all) return 0; + } else if (!match_all) { + return e1; + } + } else { + if (!match_all) return e1; + i_1 ++; + if (i_1 < t1_count) { + e1 = t1_array[i_1]; + } + } + } + + if (match_all) { + return e1; + } else { + return 0; + } +} + +/* -- Public API -- */ + +ecs_type_t ecs_type_merge( + ecs_world_t *world, + ecs_type_t type, + ecs_type_t to_add, + ecs_type_t to_remove) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This function is allowed while staged, as long as the type already + * exists. If the type does not exist yet and traversing the table graph + * results in the creation of a table, an assert will trigger. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_table_t *table = ecs_table_from_type(unsafe_world, type); + ecs_ids_t add_array = flecs_type_to_ids(to_add); + ecs_ids_t remove_array = flecs_type_to_ids(to_remove); + + table = flecs_table_traverse_remove( + unsafe_world, table, &remove_array, NULL); + + table = flecs_table_traverse_add( + unsafe_world, table, &add_array, NULL); + + if (!table) { + return NULL; + } else { + return table->type; + } +} + +static +bool has_case( + const ecs_world_t *world, + ecs_entity_t sw_case, + ecs_entity_t e) +{ + const EcsType *type_ptr = ecs_get(world, e & ECS_COMPONENT_MASK, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_type_has_id(world, type_ptr->normalized, sw_case, false); +} + +static +bool match_id( + const ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t match_with) +{ + if (ECS_HAS_ROLE(match_with, CASE)) { + ecs_entity_t sw_case = match_with & ECS_COMPONENT_MASK; + if (ECS_HAS_ROLE(id, SWITCH) && has_case(world, sw_case, id)) { + return 1; + } else { + return 0; + } + } else { + return ecs_id_match(id, match_with); + } +} + +static +int32_t search_type( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + int32_t depth, + ecs_entity_t *out) +{ + if (!id) { + return -1; + } + + if (!type) { + return -1; + } + + if (max_depth && depth > max_depth) { + return -1; + } + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); + + if (depth >= min_depth) { + if (table && !offset && !(ECS_HAS_ROLE(id, CASE))) { + ecs_table_record_t *tr = flecs_get_table_record(world, table, id); + if (tr) { + return tr->column; + } + } else { + for (i = offset; i < count; i ++) { + if (match_id(world, ids[i], id)) { + return i; + } + } + } + } + + if (rel && id != EcsPrefab && id != EcsDisabled && + id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) + { + for (i = 0; i < count; i ++) { + ecs_entity_t e = ids[i]; + if (!ECS_HAS_RELATION(e, rel)) { + continue; + } + + ecs_entity_t obj = ecs_pair_object(world, e); + ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *obj_table = ecs_get_table(world, obj); + if (!obj_table) { + continue; + } + + if ((search_type(world, obj_table, obj_table->type, 0, id, + rel, min_depth, max_depth, depth + 1, out) != -1)) + { + if (out && !*out) { + *out = obj; + } + return i; + + /* If the id could not be found on the object and the relationship + * is not IsA, try substituting the object type with IsA */ + } else if (rel != EcsIsA) { + if (search_type(world, obj_table, obj_table->type, 0, + id, EcsIsA, 1, 0, 0, out) != -1) + { + if (out && !*out) { + *out = obj; + } + return i; + } + } + } + } + + return -1; +} + +bool ecs_type_has_id( + const ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + bool owned) +{ + return search_type(world, NULL, type, 0, id, owned ? 0 : EcsIsA, 0, 0, 0, NULL) != -1; +} + +int32_t ecs_type_index_of( + ecs_type_t type, + int32_t offset, + ecs_id_t id) +{ + return search_type(NULL, NULL, type, offset, id, 0, 0, 0, 0, NULL); +} + +int32_t ecs_type_match( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_type_t type, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + int32_t min_depth, + int32_t max_depth, + ecs_entity_t *out) +{ + if (out) { + *out = 0; + } + return search_type(world, table, type, offset, id, rel, min_depth, max_depth, 0, out); +} + +bool ecs_type_has_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has) +{ + return flecs_type_contains(world, type, has, true, false) != 0; +} + +bool ecs_type_owns_type( + const ecs_world_t *world, + ecs_type_t type, + ecs_type_t has, + bool owned) +{ + return flecs_type_contains(world, type, has, true, !owned) != 0; +} + +ecs_type_t ecs_type_add( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This function is allowed while staged, as long as the type already + * exists. If the type does not exist yet and traversing the table graph + * results in the creation of a table, an assert will trigger. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_table_t *table = ecs_table_from_type(unsafe_world, type); + + ecs_ids_t entities = { + .array = &e, + .count = 1 + }; + + table = flecs_table_traverse_add(unsafe_world, table, &entities, NULL); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +ecs_type_t ecs_type_remove( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t e) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + + /* This function is allowed while staged, as long as the type already + * exists. If the type does not exist yet and traversing the table graph + * results in the creation of a table, an assert will trigger. */ + ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + + ecs_table_t *table = ecs_table_from_type(unsafe_world, type); + + ecs_ids_t entities = { + .array = &e, + .count = 1 + }; + + table = flecs_table_traverse_remove(unsafe_world, table, &entities, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + return table->type; +} + +char* ecs_type_str( + const ecs_world_t *world, + ecs_type_t type) +{ + if (!type) { + return ecs_os_strdup(""); + } + + ecs_vector_t *chbuf = ecs_vector_new(char, 32); + char *dst; + + ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + int32_t i, count = ecs_vector_count(type); + + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + char buffer[256]; + ecs_size_t len; + + if (i) { + *(char*)ecs_vector_add(&chbuf, char) = ','; + } + + if (e == 1) { + ecs_os_strcpy(buffer, "EcsComponent"); + len = ecs_os_strlen("EcsComponent"); + } else { + len = flecs_from_size_t(ecs_id_str(world, e, buffer, 256)); + } + + dst = ecs_vector_addn(&chbuf, char, len); + ecs_os_memcpy(dst, buffer, len); + } + + *(char*)ecs_vector_add(&chbuf, char) = '\0'; + + char* result = ecs_os_strdup(ecs_vector_first(chbuf, char)); + ecs_vector_free(chbuf); + return result; +} + +ecs_entity_t ecs_type_get_entity_for_xor( + ecs_world_t *world, + ecs_type_t type, + ecs_entity_t xor) +{ + ecs_assert( + ecs_type_has_id(world, type, ECS_XOR | xor, true), + ECS_INVALID_PARAMETER, NULL); + + const EcsType *type_ptr = ecs_get(world, xor, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_t xor_type = type_ptr->normalized; + ecs_assert(xor_type != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vector_count(type); + ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + for (i = 0; i < count; i ++) { + if (ecs_type_has_id(world, xor_type, array[i], true)) { + return array[i]; + } + } + + return 0; +} diff --git a/fggl/ecs2/flecs/src/vector.c b/fggl/ecs2/flecs/src/vector.c new file mode 100644 index 0000000000000000000000000000000000000000..0f32aad628f330301d774995984852c6b0f1c4e3 --- /dev/null +++ b/fggl/ecs2/flecs/src/vector.c @@ -0,0 +1,479 @@ +#include "private_api.h" + +/** Resize the vector buffer */ +static +ecs_vector_t* resize( + ecs_vector_t *vector, + int16_t offset, + int32_t size) +{ + ecs_vector_t *result = ecs_os_realloc(vector, offset + size); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); + return result; +} + +/* -- Public functions -- */ + +ecs_vector_t* _ecs_vector_new( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + result->count = 0; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; +} + +ecs_vector_t* _ecs_vector_from_array( + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count, + void *array) +{ + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_vector_t *result = + ecs_os_malloc(offset + elem_size * elem_count); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); + + result->count = elem_count; + result->size = elem_count; +#ifndef NDEBUG + result->elem_size = elem_size; +#endif + return result; +} + +void ecs_vector_free( + ecs_vector_t *vector) +{ + ecs_os_free(vector); +} + +void ecs_vector_clear( + ecs_vector_t *vector) +{ + if (vector) { + vector->count = 0; + } +} + +void _ecs_vector_zero( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + void *array = ECS_OFFSET(vector, offset); + ecs_os_memset(array, 0, elem_size * vector->count); +} + +void ecs_vector_assert_size( + ecs_vector_t *vector, + ecs_size_t elem_size) +{ + (void)elem_size; + + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + } +} + +void* _ecs_vector_addn( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); + + if (elem_count == 1) { + return _ecs_vector_add(array_inout, elem_size, offset); + } + + ecs_vector_t *vector = *array_inout; + if (!vector) { + vector = _ecs_vector_new(elem_size, offset, 1); + *array_inout = vector; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t max_count = vector->size; + int32_t old_count = vector->count; + int32_t new_count = old_count + elem_count; + + if ((new_count - 1) >= max_count) { + if (!max_count) { + max_count = elem_count; + } else { + while (max_count < new_count) { + max_count *= 2; + } + } + + vector = resize(vector, offset, max_count * elem_size); + vector->size = max_count; + *array_inout = vector; + } + + vector->count = new_count; + + return ECS_OFFSET(vector, offset + elem_size * old_count); +} + +void* _ecs_vector_add( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_vector_t *vector = *array_inout; + int32_t count, size; + + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + count = vector->count; + size = vector->size; + + if (count >= size) { + size *= 2; + if (!size) { + size = 2; + } + vector = resize(vector, offset, size * elem_size); + *array_inout = vector; + vector->size = size; + } + + vector->count = count + 1; + return ECS_OFFSET(vector, offset + elem_size * count); + } + + vector = _ecs_vector_new(elem_size, offset, 2); + *array_inout = vector; + vector->count = 1; + vector->size = 2; + return ECS_OFFSET(vector, offset); +} + +int32_t _ecs_vector_move_index( + ecs_vector_t **dst, + ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + void *dst_elem = _ecs_vector_add(dst, elem_size, offset); + void *src_elem = _ecs_vector_get(src, elem_size, offset, index); + + ecs_os_memcpy(dst_elem, src_elem, elem_size); + return _ecs_vector_remove(src, elem_size, offset, index); +} + +void ecs_vector_remove_last( + ecs_vector_t *vector) +{ + if (vector && vector->count) vector->count --; +} + +bool _ecs_vector_pop( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + void *value) +{ + if (!vector) { + return false; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + if (!count) { + return false; + } + + void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); + + if (value) { + ecs_os_memcpy(value, elem, elem_size); + } + + ecs_vector_remove_last(vector); + + return true; +} + +int32_t _ecs_vector_remove( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + void *elem = ECS_OFFSET(buffer, index * elem_size); + + ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); + + count --; + if (index != count) { + void *last_elem = ECS_OFFSET(buffer, elem_size * count); + ecs_os_memcpy(elem, last_elem, elem_size); + } + + vector->count = count; + + return count; +} + +void _ecs_vector_reclaim( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset) +{ + ecs_vector_t *vector = *array_inout; + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t size = vector->size; + int32_t count = vector->count; + + if (count < size) { + size = count; + vector = resize(vector, offset, size * elem_size); + vector->size = size; + *array_inout = vector; + } +} + +int32_t ecs_vector_count( + const ecs_vector_t *vector) +{ + if (!vector) { + return 0; + } + return vector->count; +} + +int32_t ecs_vector_size( + const ecs_vector_t *vector) +{ + if (!vector) { + return 0; + } + return vector->size; +} + +int32_t _ecs_vector_set_size( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + ecs_vector_t *vector = *array_inout; + + if (!vector) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + return elem_count; + } else { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t result = vector->size; + + if (elem_count < vector->count) { + elem_count = vector->count; + } + + if (result < elem_count) { + elem_count = flecs_next_pow_of_2(elem_count); + vector = resize(vector, offset, elem_count * elem_size); + vector->size = elem_count; + *array_inout = vector; + result = elem_count; + } + + return result; + } +} + +int32_t _ecs_vector_grow( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + int32_t current = ecs_vector_count(*array_inout); + return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); +} + +int32_t _ecs_vector_set_count( + ecs_vector_t **array_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + if (!*array_inout) { + *array_inout = _ecs_vector_new(elem_size, offset, elem_count); + } + + ecs_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + (*array_inout)->count = elem_count; + ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); + return size; +} + +void* _ecs_vector_first( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + (void)elem_size; + + ecs_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + if (vector && vector->size) { + return ECS_OFFSET(vector, offset); + } else { + return NULL; + } +} + +void* _ecs_vector_get( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t index) +{ + if (!vector) { + return NULL; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + + if (index >= count) { + return NULL; + } + + return ECS_OFFSET(vector, offset + elem_size * index); +} + +void* _ecs_vector_last( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset) +{ + if (vector) { + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + int32_t count = vector->count; + if (!count) { + return NULL; + } else { + return ECS_OFFSET(vector, offset + elem_size * (count - 1)); + } + } else { + return NULL; + } +} + +int32_t _ecs_vector_set_min_size( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + if (!*vector_inout || (*vector_inout)->size < elem_count) { + return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); + } else { + return (*vector_inout)->size; + } +} + +int32_t _ecs_vector_set_min_count( + ecs_vector_t **vector_inout, + ecs_size_t elem_size, + int16_t offset, + int32_t elem_count) +{ + _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); + + ecs_vector_t *v = *vector_inout; + if (v && v->count < elem_count) { + v->count = elem_count; + } + + return v->count; +} + +void _ecs_vector_sort( + ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + ecs_comparator_t compare_action) +{ + if (!vector) { + return; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = vector->count; + void *buffer = ECS_OFFSET(vector, offset); + + if (count > 1) { + qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); + } +} + +void _ecs_vector_memory( + const ecs_vector_t *vector, + ecs_size_t elem_size, + int16_t offset, + int32_t *allocd, + int32_t *used) +{ + if (!vector) { + return; + } + + ecs_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + + if (allocd) { + *allocd += vector->size * elem_size + offset; + } + if (used) { + *used += vector->count * elem_size; + } +} + +ecs_vector_t* _ecs_vector_copy( + const ecs_vector_t *src, + ecs_size_t elem_size, + int16_t offset) +{ + if (!src) { + return NULL; + } + + ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); + ecs_os_memcpy(dst, src, offset + elem_size * src->count); + return dst; +} diff --git a/fggl/ecs2/flecs/src/world.c b/fggl/ecs2/flecs/src/world.c new file mode 100644 index 0000000000000000000000000000000000000000..68bd0b8f379cba665287194ff7aa630115c7cf5d --- /dev/null +++ b/fggl/ecs2/flecs/src/world.c @@ -0,0 +1,1557 @@ +#include "private_api.h" + +/* Roles */ +const ecs_id_t ECS_CASE = (ECS_ROLE | (0x7Cull << 56)); +const ecs_id_t ECS_SWITCH = (ECS_ROLE | (0x7Bull << 56)); +const ecs_id_t ECS_PAIR = (ECS_ROLE | (0x7Aull << 56)); +const ecs_id_t ECS_OWNED = (ECS_ROLE | (0x75ull << 56)); +const ecs_id_t ECS_DISABLED = (ECS_ROLE | (0x74ull << 56)); + +/* Core scopes & entities */ +const ecs_entity_t EcsWorld = ECS_HI_COMPONENT_ID + 0; +const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1; +const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2; +const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 3; +const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 4; +const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 5; +const ecs_entity_t EcsHidden = ECS_HI_COMPONENT_ID + 6; + +/* Relation properties */ +const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; +const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 11; +const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 13; +const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 14; + +/* Identifier tags */ +const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 16; + +/* Relations */ +const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 21; + +/* Events */ +const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 32; +const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 33; +const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 36; +const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 37; +const ecs_entity_t EcsOnTableNonEmpty = ECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 39; +const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 40; +const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsOnComponentLifecycle = ECS_HI_COMPONENT_ID + 42; +const ecs_entity_t EcsOnDeleteObject = ECS_HI_COMPONENT_ID + 43; + +/* Actions */ +const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; +const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; +const ecs_entity_t EcsThrow = ECS_HI_COMPONENT_ID + 52; + +/* Systems */ +const ecs_entity_t EcsOnDemand = ECS_HI_COMPONENT_ID + 60; +const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; +const ecs_entity_t EcsDisabledIntern = ECS_HI_COMPONENT_ID + 62; +const ecs_entity_t EcsInactive = ECS_HI_COMPONENT_ID + 63; +const ecs_entity_t EcsPipeline = ECS_HI_COMPONENT_ID + 64; +const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; +const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; +const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; +const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68; +const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69; +const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70; +const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71; +const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72; +const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73; +const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74; + + +/* -- Private functions -- */ + +const ecs_world_t* ecs_get_world( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + return world; + } else { + return ((ecs_stage_t*)world)->world; + } +} + +const ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world) +{ + ecs_assert(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_STAGE_MAGIC, + ECS_INTERNAL_ERROR, + NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + return &world->stage; + + } else if (world->magic == ECS_STAGE_MAGIC) { + return (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(world->magic == ECS_WORLD_MAGIC || + world->magic == ECS_STAGE_MAGIC, + ECS_INTERNAL_ERROR, + NULL); + + if (world->magic == ECS_WORLD_MAGIC) { + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + return &world->stage; + + } else if (world->magic == ECS_STAGE_MAGIC) { + ecs_stage_t *stage = (ecs_stage_t*)world; + *world_ptr = stage->world; + return stage; + } + + return NULL; +} + +/* Evaluate component monitor. If a monitored entity changed it will have set a + * flag in one of the world's component monitors. Queries can register + * themselves with component monitors to determine whether they need to rematch + * with tables. */ +static +void eval_component_monitor( + ecs_world_t *world) +{ + ecs_relation_monitor_t *rm = &world->monitors; + + if (!rm->is_dirty) { + return; + } + + ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); + ecs_monitor_set_t *ms; + + while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { + if (!ms->is_dirty) { + continue; + } + + if (ms->monitors) { + ecs_map_iter_t mit = ecs_map_iter(ms->monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { + if (!m->is_dirty) { + continue; + } + + ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { + flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { + .kind = EcsQueryTableRematch + }); + }); + + m->is_dirty = false; + } + } + + ms->is_dirty = false; + } + + rm->is_dirty = false; +} + +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t relation, + ecs_entity_t id) +{ + ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + ecs_monitor_set_t *ms = ecs_map_get(world->monitors.monitor_sets, + ecs_monitor_set_t, relation); + if (ms && ms->monitors) { + ecs_monitor_t *m = ecs_map_get(ms->monitors, + ecs_monitor_t, id); + if (m) { + m->is_dirty = true; + ms->is_dirty = true; + world->monitors.is_dirty = true; + } + } +} + +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t relation, + 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); + ecs_assert(world->monitors.monitor_sets != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_monitor_set_t *ms = ecs_map_ensure( + world->monitors.monitor_sets, ecs_monitor_set_t, relation); + ecs_assert(ms != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!ms->monitors) { + ms->monitors = ecs_map_new(ecs_monitor_t, 1); + } + + ecs_monitor_t *m = ecs_map_ensure(ms->monitors, ecs_monitor_t, id); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_query_t **q = ecs_vector_add(&m->queries, ecs_query_t*); + *q = query; +} + +static +void monitors_init( + ecs_relation_monitor_t *rm) +{ + rm->monitor_sets = ecs_map_new(ecs_monitor_t, 0); + rm->is_dirty = false; +} + +static +void monitors_fini( + ecs_relation_monitor_t *rm) +{ + ecs_map_iter_t it = ecs_map_iter(rm->monitor_sets); + ecs_monitor_set_t *ms; + + while ((ms = ecs_map_next(&it, ecs_monitor_set_t, NULL))) { + if (ms->monitors) { + ecs_map_iter_t mit = ecs_map_iter(ms->monitors); + ecs_monitor_t *m; + while ((m = ecs_map_next(&mit, ecs_monitor_t, NULL))) { + ecs_vector_free(m->queries); + } + + ecs_map_free(ms->monitors); + } + } + + ecs_map_free(rm->monitor_sets); +} + +static +void init_store( + ecs_world_t *world) +{ + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + /* Initialize entity index */ + world->store.entity_index = flecs_sparse_new(ecs_record_t); + flecs_sparse_set_id_source(world->store.entity_index, &world->stats.last_id); + + /* Initialize root table */ + world->store.tables = flecs_sparse_new(ecs_table_t); + + /* Initialize table map */ + world->store.table_map = flecs_table_hashmap_new(); + + /* Initialize one root table per stage */ + flecs_init_root_table(world); +} + +static +void clean_tables( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->store.tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + flecs_table_free(world, t); + } + + /* Free table types separately so that if application destructors rely on + * a type it's still valid. */ + for (i = 0; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense(world->store.tables, ecs_table_t, i); + flecs_table_free_type(t); + } + + /* Clear the root table */ + if (count) { + flecs_table_reset(world, &world->store.root); + } +} + +static +void fini_store(ecs_world_t *world) { + clean_tables(world); + flecs_sparse_free(world->store.tables); + flecs_table_free(world, &world->store.root); + flecs_sparse_clear(world->store.entity_index); + flecs_hashmap_free(world->store.table_map); +} + +/* -- Public functions -- */ + +ecs_world_t *ecs_mini(void) { + ecs_os_init(); + + ecs_trace_1("bootstrap"); + ecs_log_push(); + + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); + } + + if (!ecs_os_has_threading()) { + ecs_trace_1("threading not available"); + } + + if (!ecs_os_has_time()) { + ecs_trace_1("time management not available"); + } + + ecs_world_t *world = ecs_os_calloc(sizeof(ecs_world_t)); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + + world->magic = ECS_WORLD_MAGIC; + world->fini_actions = NULL; + + world->type_info = flecs_sparse_new(ecs_type_info_t); + world->id_index = ecs_map_new(ecs_id_record_t, 8); + world->id_triggers = ecs_map_new(ecs_id_trigger_t, 8); + + world->queries = flecs_sparse_new(ecs_query_t); + world->triggers = flecs_sparse_new(ecs_trigger_t); + world->observers = flecs_sparse_new(ecs_observer_t); + world->fini_tasks = ecs_vector_new(ecs_entity_t, 0); + world->name_prefix = NULL; + + world->aliases = flecs_string_hashmap_new(); + world->symbols = flecs_string_hashmap_new(); + + monitors_init(&world->monitors); + + world->type_handles = ecs_map_new(ecs_entity_t, 0); + world->on_activate_components = ecs_map_new(ecs_on_demand_in_t, 0); + world->on_enable_components = ecs_map_new(ecs_on_demand_in_t, 0); + + world->worker_stages = NULL; + world->workers_waiting = 0; + world->workers_running = 0; + world->quit_workers = false; + world->is_readonly = false; + world->is_fini = false; + world->measure_frame_time = false; + world->measure_system_time = false; + world->should_quit = false; + world->locking_enabled = false; + world->pipeline = 0; + + world->frame_start_time = (ecs_time_t){0, 0}; + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } + + world->stats.target_fps = 0; + world->stats.last_id = 0; + + world->stats.delta_time_raw = 0; + world->stats.delta_time = 0; + world->stats.time_scale = 1.0; + world->stats.frame_time_total = 0; + world->stats.system_time_total = 0; + world->stats.merge_time_total = 0; + world->stats.world_time_total = 0; + world->stats.frame_count_total = 0; + world->stats.merge_count_total = 0; + world->stats.systems_ran_frame = 0; + world->stats.pipeline_build_count_total = 0; + + world->range_check_enabled = false; + + world->fps_sleep = 0; + + world->context = NULL; + + world->arg_fps = 0; + world->arg_threads = 0; + + flecs_stage_init(world, &world->stage); + ecs_set_stages(world, 1); + + init_store(world); + + flecs_bootstrap(world); + + ecs_log_pop(); + + return world; +} + +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); + +#ifdef FLECS_MODULE_H + ecs_trace_1("import builtin modules"); + ecs_log_push(); +#ifdef FLECS_SYSTEM_H + ECS_IMPORT(world, FlecsSystem); +#endif +#ifdef FLECS_PIPELINE_H + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER_H + ECS_IMPORT(world, FlecsTimer); +#endif + ecs_log_pop(); +#endif + + return world; +} + +#define ARG(short, long, action)\ + if (i < argc) {\ + if (argv[i][0] == '-') {\ + if (argv[i][1] == '-') {\ + if (long && !strcmp(&argv[i][2], long ? long : "")) {\ + action;\ + parsed = true;\ + }\ + } else {\ + if (short && argv[i][1] == short) {\ + action;\ + parsed = true;\ + }\ + }\ + }\ + } + +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) +{ + (void)argc; + (void)argv; + return ecs_init(); +} + +void ecs_quit( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + world->should_quit = true; +} + +bool ecs_should_quit( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->should_quit; +} + +static +void on_demand_in_map_fini( + ecs_map_t *map) +{ + ecs_map_iter_t it = ecs_map_iter(map); + ecs_on_demand_in_t *elem; + + while ((elem = ecs_map_next(&it, ecs_on_demand_in_t, NULL))) { + ecs_vector_free(elem->systems); + } + + ecs_map_free(map); +} + +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + /* If no id is specified, broadcast to all tables */ + if (!id) { + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_notify(world, table, event); + } + + /* If id is specified, only broadcast to tables with id */ + } else { + ecs_id_record_t *r = flecs_get_id_record(world, id); + if (!r) { + return; + } + + ecs_table_record_t *tr; + ecs_map_iter_t it = ecs_map_iter(r->table_index); + while ((tr = ecs_map_next(&it, ecs_table_record_t, NULL))) { + flecs_table_notify(world, tr->table, event); + } + } +} + +static +void default_ctor( + ecs_world_t *world, ecs_entity_t component, const ecs_entity_t *entity_ptr, + void *ptr, size_t size, int32_t count, void *ctx) +{ + (void)world; (void)component; (void)entity_ptr; (void)ctx; + ecs_os_memset(ptr, 0, flecs_from_size_t(size) * count); +} + +static +void default_copy_ctor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, const void *src_ptr, + size_t size, int32_t count, void *ctx) +{ + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->copy(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); +} + +static +void default_move_ctor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); +} + +static +void default_ctor_w_move_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->ctor(world, component, dst_entity, dst_ptr, size, count, ctx); + callbacks->move(world, component, dst_entity, src_entity, dst_ptr, src_ptr, + size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} + +static +void default_move_ctor_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->move_ctor(world, component, callbacks, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} + +static +void default_move( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + callbacks->move(world, component, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); +} + +static +void default_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + (void)callbacks; + (void)src_entity; + + /* 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. */ + callbacks->dtor(world, component, dst_entity, dst_ptr, size, count, ctx); + ecs_os_memcpy(dst_ptr, src_ptr, flecs_from_size_t(size) * count); +} + +static +void default_move_w_dtor( + ecs_world_t *world, ecs_entity_t component, + const EcsComponentLifecycle *callbacks, const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, void *dst_ptr, void *src_ptr, size_t size, + int32_t count, void *ctx) +{ + /* 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. */ + callbacks->move(world, component, dst_entity, src_entity, + dst_ptr, src_ptr, size, count, ctx); + callbacks->dtor(world, component, src_entity, src_ptr, size, count, ctx); +} + +void ecs_set_component_actions_w_id( + ecs_world_t *world, + ecs_entity_t component, + EcsComponentLifecycle *lifecycle) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + + const EcsComponent *component_ptr = ecs_get(world, component, EcsComponent); + + /* Cannot register lifecycle actions for things that aren't a component */ + ecs_assert(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Cannot register lifecycle actions for components with size 0 */ + ecs_assert(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_type_info_t *c_info = flecs_get_or_create_c_info(world, component); + ecs_assert(c_info != NULL, ECS_INTERNAL_ERROR, NULL); + + if (c_info->lifecycle_set) { + ecs_assert(c_info->component == component, ECS_INTERNAL_ERROR, NULL); + ecs_assert(c_info->lifecycle.ctor == lifecycle->ctor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.dtor == lifecycle->dtor, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.copy == lifecycle->copy, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_assert(c_info->lifecycle.move == lifecycle->move, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + } else { + c_info->component = component; + c_info->lifecycle = *lifecycle; + c_info->lifecycle_set = true; + c_info->size = component_ptr->size; + c_info->alignment = component_ptr->alignment; + + /* If no constructor is set, invoking any of the other lifecycle actions + * is not safe as they will potentially access uninitialized memory. For + * ease of use, if no constructor is specified, set a default one that + * initializes the component to 0. */ + if (!lifecycle->ctor && + (lifecycle->dtor || lifecycle->copy || lifecycle->move)) + { + c_info->lifecycle.ctor = default_ctor; + } + + /* Set default copy ctor, move ctor and merge */ + if (lifecycle->copy && !lifecycle->copy_ctor) { + c_info->lifecycle.copy_ctor = default_copy_ctor; + } + + if (lifecycle->move && !lifecycle->move_ctor) { + c_info->lifecycle.move_ctor = default_move_ctor; + } + + if (!lifecycle->ctor_move_dtor) { + if (lifecycle->move) { + if (lifecycle->dtor) { + if (lifecycle->move_ctor) { + /* If an explicit move ctor has been set, use callback + * that uses the move ctor vs. using a ctor+move */ + c_info->lifecycle.ctor_move_dtor = + default_move_ctor_w_dtor; + } else { + /* If no explicit move_ctor has been set, use + * combination of ctor + move + dtor */ + c_info->lifecycle.ctor_move_dtor = + default_ctor_w_move_w_dtor; + } + } else { + /* If no dtor has been set, this is just a move ctor */ + c_info->lifecycle.ctor_move_dtor = + c_info->lifecycle.move_ctor; + } + } + } + + if (!lifecycle->move_dtor) { + if (lifecycle->move) { + if (lifecycle->dtor) { + c_info->lifecycle.move_dtor = default_move_w_dtor; + } else { + c_info->lifecycle.move_dtor = default_move; + } + } else { + if (lifecycle->dtor) { + c_info->lifecycle.move_dtor = default_dtor; + } + } + } + + /* Broadcast to all tables since we need to register a ctor for every + * table that uses the component as itself, as predicate or as object. + * The latter is what makes selecting the right set of tables complex, + * as it depends on the predicate of a pair whether the object is used + * as the component type or not. + * A more selective approach requires a more expressive notification + * framework. */ + flecs_notify_tables(world, 0, &(ecs_table_event_t) { + .kind = EcsTableComponentInfo, + .component = component + }); + } +} + +bool ecs_component_has_actions( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + + const ecs_type_info_t *c_info = flecs_get_c_info(world, component); + return (c_info != NULL) && c_info->lifecycle_set; +} + +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +} + +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(action != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_action_elem_t *elem = ecs_vector_add(&stage->post_frame_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +} + +/* Unset data in tables */ +static +void fini_unset_tables( + ecs_world_t *world) +{ + ecs_sparse_t *tables = world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); + flecs_table_remove_actions(world, table); + } +} + +/* Invoke fini actions */ +static +void fini_actions( + ecs_world_t *world) +{ + ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { + elem->action(world, elem->ctx); + }); + + ecs_vector_free(world->fini_actions); +} + +/* Cleanup component lifecycle callbacks & systems */ +static +void fini_component_lifecycle( + ecs_world_t *world) +{ + flecs_sparse_free(world->type_info); +} + +/* Cleanup queries */ +static +void fini_queries( + ecs_world_t *world) +{ + int32_t i, count = flecs_sparse_count(world->queries); + for (i = 0; i < count; i ++) { + ecs_query_t *query = flecs_sparse_get_dense(world->queries, ecs_query_t, 0); + ecs_query_fini(query); + } + flecs_sparse_free(world->queries); +} + +static +void fini_observers( + ecs_world_t *world) +{ + flecs_sparse_free(world->observers); +} + +/* Cleanup stages */ +static +void fini_stages( + ecs_world_t *world) +{ + flecs_stage_deinit(world, &world->stage); + ecs_set_stages(world, 0); +} + +/* Cleanup id index */ +static +void fini_id_index( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(world->id_index); + ecs_id_record_t *r; + while ((r = ecs_map_next(&it, ecs_id_record_t, NULL))) { + ecs_map_free(r->table_index); + } + + ecs_map_free(world->id_index); +} + +static +void fini_id_triggers( + ecs_world_t *world) +{ + ecs_map_iter_t it = ecs_map_iter(world->id_triggers); + ecs_id_trigger_t *t; + while ((t = ecs_map_next(&it, ecs_id_trigger_t, NULL))) { + ecs_map_free(t->on_add_triggers); + ecs_map_free(t->on_remove_triggers); + ecs_map_free(t->on_set_triggers); + ecs_map_free(t->un_set_triggers); + } + ecs_map_free(world->id_triggers); + flecs_sparse_free(world->triggers); +} + +/* Cleanup aliases & symbols */ +static +void fini_aliases( + ecs_hashmap_t *map) +{ + flecs_hashmap_iter_t it = flecs_hashmap_iter(*map); + ecs_string_t *key; + while (flecs_hashmap_next_w_key(&it, ecs_string_t, &key, ecs_entity_t)) { + ecs_os_free(key->value); + } + + flecs_hashmap_free(*map); +} + +/* Cleanup misc structures */ +static +void fini_misc( + ecs_world_t *world) +{ + on_demand_in_map_fini(world->on_activate_components); + on_demand_in_map_fini(world->on_enable_components); + ecs_map_free(world->type_handles); + ecs_vector_free(world->fini_tasks); + monitors_fini(&world->monitors); +} + +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + + world->is_fini = true; + + /* Operations invoked during UnSet/OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + ecs_defer_begin(world); + + /* Run UnSet/OnRemove actions for components while the store is still + * unmodified by cleanup. */ + fini_unset_tables(world); + + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + fini_actions(world); + + /* This will destroy all entities and components. After this point no more + * user code is executed. */ + 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->stage); + + /* Entity index is kept alive until this point so that user code can do + * validity checks on entity ids, even though after store cleanup the index + * will be empty, so all entity ids are invalid. */ + flecs_sparse_free(world->store.entity_index); + + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + } + + fini_stages(world); + + fini_component_lifecycle(world); + + fini_queries(world); + + fini_observers(world); + + fini_id_index(world); + + fini_id_triggers(world); + + fini_aliases(&world->aliases); + + fini_aliases(&world->symbols); + + fini_misc(world); + + /* In case the application tries to use the memory of the freed world, this + * will trigger an assert */ + world->magic = 0; + + flecs_increase_timer_resolution(0); + + /* End of the world */ + ecs_os_free(world); + + ecs_os_fini(); + + return 0; +} + +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_PARAMETER, NULL); + ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID); +} + +void flecs_eval_component_monitors( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INTERNAL_ERROR, NULL); + eval_component_monitor(world); +} + +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (world->stats.target_fps == 0.0f || enable) { + world->measure_frame_time = enable; + } +} + +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + world->measure_system_time = enable; +} + +/* Increase timer resolution based on target fps */ +static +void set_timer_resolution( + FLECS_FLOAT fps) +{ + if(fps >= 60.0f) flecs_increase_timer_resolution(1); + else flecs_increase_timer_resolution(0); +} + +void ecs_set_target_fps( + ecs_world_t *world, + FLECS_FLOAT fps) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (!world->arg_fps) { + ecs_measure_frame_time(world, true); + world->stats.target_fps = fps; + set_timer_resolution(fps); + } +} + +void* ecs_get_context( + const ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->context; +} + +void ecs_set_context( + ecs_world_t *world, + void *context) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + world->context = context; +} + +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!id_end || id_end > world->stats.last_id, ECS_INVALID_PARAMETER, NULL); + + if (world->stats.last_id < id_start) { + world->stats.last_id = id_start - 1; + } + + world->stats.min_id = id_start; + world->stats.max_id = id_end; +} + +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; +} + +int32_t ecs_get_threads( + ecs_world_t *world) +{ + return ecs_vector_count(world->worker_stages); +} + +bool ecs_enable_locking( + ecs_world_t *world, + bool enable) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + if (enable) { + if (!world->locking_enabled) { + world->mutex = ecs_os_mutex_new(); + world->thr_sync = ecs_os_mutex_new(); + world->thr_cond = ecs_os_cond_new(); + } + } else { + if (world->locking_enabled) { + ecs_os_mutex_free(world->mutex); + ecs_os_mutex_free(world->thr_sync); + ecs_os_cond_free(world->thr_cond); + } + } + + bool old = world->locking_enabled; + world->locking_enabled = enable; + return old; +} + +void ecs_lock( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->mutex); +} + +void ecs_unlock( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->mutex); +} + +void ecs_begin_wait( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_wait(world->thr_cond, world->thr_sync); +} + +void ecs_end_wait( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); + ecs_os_mutex_unlock(world->thr_sync); +} + +const ecs_type_info_t * flecs_get_c_info( + const ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL); + + return flecs_sparse_get(world->type_info, ecs_type_info_t, component); +} + +ecs_type_info_t * flecs_get_or_create_c_info( + ecs_world_t *world, + ecs_entity_t component) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + const ecs_type_info_t *c_info = flecs_get_c_info(world, component); + ecs_type_info_t *c_info_mut = NULL; + if (!c_info) { + c_info_mut = flecs_sparse_ensure( + world->type_info, ecs_type_info_t, component); + ecs_assert(c_info_mut != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + c_info_mut = (ecs_type_info_t*)c_info; + } + + return c_info_mut; +} + +static +FLECS_FLOAT insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + ecs_time_t start = *stop; + FLECS_FLOAT delta_time = (FLECS_FLOAT)ecs_time_measure(stop); + + if (world->stats.target_fps == (FLECS_FLOAT)0.0) { + return delta_time; + } + + FLECS_FLOAT target_delta_time = + ((FLECS_FLOAT)1.0 / (FLECS_FLOAT)world->stats.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. */ + FLECS_FLOAT sleep = target_delta_time - delta_time; + + /* Pick a sleep interval that is 20 times lower than the time one frame + * should take. This means that this function at most iterates 20 times in + * a busy loop */ + FLECS_FLOAT sleep_time = sleep / (FLECS_FLOAT)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 (sleep_time != 0) { + ecs_sleepf((double)sleep_time); + } + + ecs_time_t now = start; + delta_time = (FLECS_FLOAT)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (FLECS_FLOAT)2.0)); + + return delta_time; +} + +static +FLECS_FLOAT start_measure_frame( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + FLECS_FLOAT delta_time = 0; + + if (world->measure_frame_time || (user_delta_time == 0)) { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.sec) { + delta_time = insert_sleep(world, &t); + + ecs_time_measure(&t); + } else { + ecs_time_measure(&t); + if (world->stats.target_fps != 0) { + delta_time = (FLECS_FLOAT)1.0 / world->stats.target_fps; + } else { + /* Best guess */ + delta_time = (FLECS_FLOAT)1.0 / (FLECS_FLOAT)60.0; + } + } + + /* Keep trying while delta_time is zero */ + } while (delta_time == 0); + + world->frame_start_time = t; + + /* Keep track of total time passed in world */ + world->stats.world_time_total_raw += (FLECS_FLOAT)delta_time; + } + + return (FLECS_FLOAT)delta_time; +} + +static +void stop_measure_frame( + ecs_world_t* world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + if (world->measure_frame_time) { + ecs_time_t t = world->frame_start_time; + world->stats.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t); + } +} + +FLECS_FLOAT ecs_frame_begin( + ecs_world_t *world, + FLECS_FLOAT user_delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); + + ecs_assert(user_delta_time != 0 || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); + + if (world->locking_enabled) { + ecs_lock(world); + } + + /* Start measuring total frame time */ + FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time); + if (user_delta_time == 0) { + user_delta_time = delta_time; + } + + world->stats.delta_time_raw = user_delta_time; + world->stats.delta_time = user_delta_time * world->stats.time_scale; + + /* Keep track of total scaled time passed in world */ + world->stats.world_time_total += world->stats.delta_time; + + flecs_eval_component_monitors(world); + + return world->stats.delta_time; +} + +void ecs_frame_end( + ecs_world_t *world) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + ecs_assert(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); + + world->stats.frame_count_total ++; + + ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { + flecs_stage_merge_post_frame(world, stage); + }); + + if (world->locking_enabled) { + ecs_unlock(world); + + ecs_os_mutex_lock(world->thr_sync); + ecs_os_cond_broadcast(world->thr_cond); + ecs_os_mutex_unlock(world->thr_sync); + } + + stop_measure_frame(world); +} + +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return &world->stats; +} + +void flecs_notify_queries( + ecs_world_t *world, + ecs_query_event_t *event) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + int32_t i, count = flecs_sparse_count(world->queries); + for (i = 0; i < count; i ++) { + flecs_query_notify(world, + flecs_sparse_get_dense(world->queries, ecs_query_t, i), event); + } +} + +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world->magic == ECS_WORLD_MAGIC, ECS_INVALID_OPERATION, NULL); + + /* Notify queries that table is to be removed */ + flecs_notify_queries( + world, &(ecs_query_event_t){ + .kind = EcsQueryTableUnmatch, + .table = table + }); + + uint64_t id = table->id; + + /* Free resources associated with table */ + flecs_table_free(world, table); + flecs_table_free_type(table); + + /* Remove table from sparse set */ + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + flecs_sparse_remove(world->store.tables, id); +} + +static +void register_table_for_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + ecs_id_record_t *r = flecs_ensure_id_record(world, id); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!r->table_index) { + r->table_index = ecs_map_new(ecs_table_record_t, 1); + } + + ecs_table_record_t *tr = ecs_map_ensure( + r->table_index, ecs_table_record_t, table->id); + + /* A table can be registered for the same entity multiple times if this is + * a trait. In that case make sure the column with the first occurrence is + * registered with the index */ + if (!tr->table || column < tr->column) { + tr->table = table; + tr->column = column; + tr->count = 1; + } else { + tr->count ++; + } + + char buf[255]; ecs_id_str(world, id, buf, 255); + + /* Set flags if triggers are registered for table */ + if (!(table->flags & EcsTableIsDisabled)) { + if (flecs_triggers_get(world, id, EcsOnAdd)) { + table->flags |= EcsTableHasOnAdd; + } + if (flecs_triggers_get(world, id, EcsOnRemove)) { + table->flags |= EcsTableHasOnRemove; + } + if (flecs_triggers_get(world, id, EcsOnSet)) { + table->flags |= EcsTableHasOnSet; + } + if (flecs_triggers_get(world, id, EcsUnSet)) { + table->flags |= EcsTableHasUnSet; + } + } +} + +static +void unregister_table_for_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_record_t *r = flecs_get_id_record(world, id); + if (!r || !r->table_index) { + return; + } + + ecs_map_remove(r->table_index, table->id); + if (!ecs_map_count(r->table_index)) { + flecs_clear_id_record(world, id); + } +} + +static +void do_register_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + int32_t column, + bool unregister) +{ + if (unregister) { + unregister_table_for_id(world, table, id); + } else { + register_table_for_id(world, table, id, column); + } +} + +static +void do_register_each_id( + ecs_world_t *world, + ecs_table_t *table, + bool unregister) +{ + int32_t i, count = ecs_vector_count(table->type); + ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); + bool has_childof = false; + + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; + + /* This check ensures that legacy CHILDOF works */ + if (ECS_HAS_RELATION(id, EcsChildOf)) { + has_childof = true; + } + + do_register_id(world, table, id, i, unregister); + do_register_id(world, table, EcsWildcard, i, unregister); + + if (ECS_HAS_ROLE(id, PAIR)) { + ecs_entity_t pred_w_wildcard = ecs_pair( + ECS_PAIR_RELATION(id), EcsWildcard); + do_register_id(world, table, pred_w_wildcard, i, unregister); + + ecs_entity_t obj_w_wildcard = ecs_pair( + EcsWildcard, ECS_PAIR_OBJECT(id)); + do_register_id(world, table, obj_w_wildcard, i, unregister); + + ecs_entity_t all_wildcard = ecs_pair(EcsWildcard, EcsWildcard); + do_register_id(world, table, all_wildcard, i, unregister); + + if (!unregister) { + flecs_set_watch(world, ecs_pair_relation(world, id)); + flecs_set_watch(world, ecs_pair_object(world, id)); + } + } else { + if (id & ECS_ROLE_MASK) { + id &= ECS_COMPONENT_MASK; + do_register_id(world, table, ecs_pair(id, EcsWildcard), + i, unregister); + } + + if (!unregister) { + flecs_set_watch(world, id); + } + } + } + + if (!has_childof) { + do_register_id(world, table, ecs_pair(EcsChildOf, 0), 0, unregister); + } +} + +void flecs_register_table( + ecs_world_t *world, + ecs_table_t *table) +{ + do_register_each_id(world, table, false); +} + +void flecs_unregister_table( + ecs_world_t *world, + ecs_table_t *table) +{ + /* Remove table from id indices */ + do_register_each_id(world, table, true); + + /* Remove table from table map */ + ecs_ids_t key = { + .array = ecs_vector_first(table->type, ecs_id_t), + .count = ecs_vector_count(table->type) + }; + + flecs_hashmap_remove(world->store.table_map, &key, ecs_table_t*); +} + +ecs_id_record_t* flecs_ensure_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + return ecs_map_ensure(world->id_index, ecs_id_record_t, id); +} + +ecs_id_record_t* flecs_get_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + return ecs_map_get(world->id_index, ecs_id_record_t, id); +} + +ecs_table_record_t* flecs_get_table_record( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_record_t* idr = flecs_get_id_record(world, id); + if (!idr) { + return NULL; + } + + return ecs_map_get(idr->table_index, ecs_table_record_t, table->id); +} + +void flecs_clear_id_record( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_id_record_t *r = flecs_get_id_record(world, id); + if (!r) { + return; + } + + ecs_map_free(r->table_index); + ecs_map_remove(world->id_index, id); +} diff --git a/fggl/ecs2/flecs/templates/c/flecs-graphics/.gitignore b/fggl/ecs2/flecs/templates/c/flecs-graphics/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33560686714c750d281191605c7dd92518608fa9 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-graphics/.gitignore @@ -0,0 +1,4 @@ +.bake_cache +.DS_Store +.vscode +bin diff --git a/fggl/ecs2/flecs/templates/c/flecs-graphics/include/__id_underscore.h b/fggl/ecs2/flecs/templates/c/flecs-graphics/include/__id_underscore.h new file mode 100644 index 0000000000000000000000000000000000000000..93984710e471207f8eceeff8483b99bf5839bcc2 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-graphics/include/__id_underscore.h @@ -0,0 +1,16 @@ +#ifndef ${id upper}_H +#define ${id upper}_H + +/* This generated file contains includes for project dependencies */ +#include "${id dash}/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/templates/c/flecs-graphics/project.json b/fggl/ecs2/flecs/templates/c/flecs-graphics/project.json new file mode 100644 index 0000000000000000000000000000000000000000..2ea02bdd2c314b8b1073239a9f59cb46f9fa8b76 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-graphics/project.json @@ -0,0 +1,16 @@ +{ + "id": "flecs.graphics", + "type": "template", + "value": { + "author": "Jane Doe", + "description": "Flecs graphics application", + "use": [ + "flecs", + "flecs.components.transform", + "flecs.components.geometry", + "flecs.components.graphics", + "flecs.components.input", + "flecs.systems.sdl2" + ] + } +} diff --git a/fggl/ecs2/flecs/templates/c/flecs-graphics/src/main.c b/fggl/ecs2/flecs/templates/c/flecs-graphics/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..aff7f1e6d876a457d8c900fc24bc8340dc1f3b5c --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-graphics/src/main.c @@ -0,0 +1,50 @@ +#include <${id underscore}.h> + +#define MOVE_SPEED (5) + +void MoveSquare(ecs_iter_t *it) { + EcsInput *input = ecs_term(it, EcsInput, 1); + EcsPosition2D *position = ecs_term(it, EcsPosition2D, 2); + + int x_v = input->keys[ECS_KEY_D].state || input->keys[ECS_KEY_RIGHT].state; + x_v -= input->keys[ECS_KEY_A].state || input->keys[ECS_KEY_LEFT].state; + int y_v = input->keys[ECS_KEY_S].state || input->keys[ECS_KEY_DOWN].state; + y_v -= input->keys[ECS_KEY_W].state || input->keys[ECS_KEY_UP].state; + + position->x += x_v * MOVE_SPEED; + position->y += y_v * MOVE_SPEED; +} + +int main(int argc, char *argv[]) { + ecs_world_t *world = ecs_init_w_args(argc, argv); + + ECS_IMPORT(world, FlecsComponentsTransform); + ECS_IMPORT(world, FlecsComponentsGeometry); + ECS_IMPORT(world, FlecsComponentsGraphics); + ECS_IMPORT(world, FlecsComponentsInput); + ECS_IMPORT(world, FlecsSystemsSdl2); + + /* Define entity for square */ + ECS_ENTITY(world, Square, EcsPosition2D, EcsSquare, EcsRgb); + + /* Define system that moves the square on keyboard input */ + ECS_SYSTEM(world, MoveSquare, EcsOnUpdate, EcsInput, Square.EcsPosition2D); + + /* Initialize canvas */ + ecs_set(world, 0, EcsCanvas2D, { + .window = { .width = 800, .height = 600 }, .title = "Hello ${id}!" + }); + + /* Initialize square */ + ecs_set(world, Square, EcsPosition2D, {0, 0}); + ecs_set(world, Square, EcsSquare, { .size = 50 }); + ecs_set(world, Square, EcsRgb, { .r = 0, .g = 0, .b = 255, .a = 255 }); + + /* Enter main loop */ + ecs_set_target_fps(world, 60); + + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/templates/c/flecs-module/.gitignore b/fggl/ecs2/flecs/templates/c/flecs-module/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33560686714c750d281191605c7dd92518608fa9 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-module/.gitignore @@ -0,0 +1,4 @@ +.bake_cache +.DS_Store +.vscode +bin diff --git a/fggl/ecs2/flecs/templates/c/flecs-module/include/__id_underscore.h b/fggl/ecs2/flecs/templates/c/flecs-module/include/__id_underscore.h new file mode 100644 index 0000000000000000000000000000000000000000..2b34489210b8c116e9f7eb9c9fd2b616d6b2adb1 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-module/include/__id_underscore.h @@ -0,0 +1,34 @@ +#ifndef ${id upper}_H +#define ${id upper}_H + +/* This generated file contains includes for project dependencies */ +#include "${id dash}/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +typedef int MyComponent; +*/ + +typedef struct ${id pascalcase} { + /* + * ECS_DECLARE_ENTITY(MyTag); + * ECS_DECLARE_COMPONENT(MyComponent); + */ +} ${id pascalcase}; + +void ${id pascalcase}Import( + ecs_world_t *world); + +#define ${id pascalcase}ImportHandles(handles) /*\ + * ECS_IMPORT_ENTITY(handles, MyTag);\ + * ECS_IMPORT_COMPONENT(handles, MyComponent); + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/fggl/ecs2/flecs/templates/c/flecs-module/project.json b/fggl/ecs2/flecs/templates/c/flecs-module/project.json new file mode 100644 index 0000000000000000000000000000000000000000..d357d5ef0f8f5e0c4bf173d3b6bae69ff3dae327 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-module/project.json @@ -0,0 +1,9 @@ +{ + "id": "flecs.module", + "type": "template", + "value": { + "author": "Sander Mertens", + "description": "Flecs module package", + "use": ["flecs"] + } +} diff --git a/fggl/ecs2/flecs/templates/c/flecs-module/src/main.c b/fggl/ecs2/flecs/templates/c/flecs-module/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..d39ed5221425ee48e39384272d3a5e4c9a96c1d9 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs-module/src/main.c @@ -0,0 +1,15 @@ +#include <${id underscore}.h> + +void ${id pascalcase}Import( + ecs_world_t *world) +{ + ECS_MODULE(world, ${id pascalcase}); + + /* + * ECS_COMPONENT(world, MyComponent); + * ECS_ENTITY(world, MyTag, 0); + * + * ECS_EXPORT_COMPONENT(MyComponent); + * ECS_EXPORT_ENTITY(MyTag); + */ +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/templates/c/flecs/.gitignore b/fggl/ecs2/flecs/templates/c/flecs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33560686714c750d281191605c7dd92518608fa9 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs/.gitignore @@ -0,0 +1,4 @@ +.bake_cache +.DS_Store +.vscode +bin diff --git a/fggl/ecs2/flecs/templates/c/flecs/include/__id_underscore.h b/fggl/ecs2/flecs/templates/c/flecs/include/__id_underscore.h new file mode 100644 index 0000000000000000000000000000000000000000..93984710e471207f8eceeff8483b99bf5839bcc2 --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs/include/__id_underscore.h @@ -0,0 +1,16 @@ +#ifndef ${id upper}_H +#define ${id upper}_H + +/* This generated file contains includes for project dependencies */ +#include "${id dash}/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/templates/c/flecs/project.json b/fggl/ecs2/flecs/templates/c/flecs/project.json new file mode 100644 index 0000000000000000000000000000000000000000..25ceb08ce42eabc0bb07290e81c99ef45ad7736f --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs/project.json @@ -0,0 +1,9 @@ +{ + "id": "flecs", + "type": "template", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": ["flecs"] + } +} diff --git a/fggl/ecs2/flecs/templates/c/flecs/src/main.c b/fggl/ecs2/flecs/templates/c/flecs/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..4bee866ab84a324204384955fba1104d943b815e --- /dev/null +++ b/fggl/ecs2/flecs/templates/c/flecs/src/main.c @@ -0,0 +1,18 @@ +#include <${id underscore}.h> + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + ecs_world_t *world = ecs_init_w_args(argc, argv); + + /* Set target FPS for main loop */ + ecs_set_target_fps(world, 60); + + printf("Application ${id} is running, press CTRL-C to exit...\n"); + + /* Run systems */ + while ( ecs_progress(world, 0)); + + /* Cleanup */ + return ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/templates/cpp/flecs/.gitignore b/fggl/ecs2/flecs/templates/cpp/flecs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33560686714c750d281191605c7dd92518608fa9 --- /dev/null +++ b/fggl/ecs2/flecs/templates/cpp/flecs/.gitignore @@ -0,0 +1,4 @@ +.bake_cache +.DS_Store +.vscode +bin diff --git a/fggl/ecs2/flecs/templates/cpp/flecs/include/__id_underscore.h b/fggl/ecs2/flecs/templates/cpp/flecs/include/__id_underscore.h new file mode 100644 index 0000000000000000000000000000000000000000..93984710e471207f8eceeff8483b99bf5839bcc2 --- /dev/null +++ b/fggl/ecs2/flecs/templates/cpp/flecs/include/__id_underscore.h @@ -0,0 +1,16 @@ +#ifndef ${id upper}_H +#define ${id upper}_H + +/* This generated file contains includes for project dependencies */ +#include "${id dash}/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/templates/cpp/flecs/project.json b/fggl/ecs2/flecs/templates/cpp/flecs/project.json new file mode 100644 index 0000000000000000000000000000000000000000..a071f3a35a9628c9e111fcb4cea97883d871cd77 --- /dev/null +++ b/fggl/ecs2/flecs/templates/cpp/flecs/project.json @@ -0,0 +1,10 @@ +{ + "id": "flecs", + "type": "template", + "value": { + "author": "Jane Doe", + "description": "A simple hello world flecs application", + "use": ["flecs"], + "language": "c++" + } +} diff --git a/fggl/ecs2/flecs/templates/cpp/flecs/src/main.cpp b/fggl/ecs2/flecs/templates/cpp/flecs/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7396eab81f2f6676d882bc1d737b9670cf166394 --- /dev/null +++ b/fggl/ecs2/flecs/templates/cpp/flecs/src/main.cpp @@ -0,0 +1,15 @@ +#include <${id underscore}.h> +#include <iostream> + +int main(int argc, char *argv[]) { + /* Create the world, pass arguments for overriding the number of threads,fps + * or for starting the admin dashboard (see flecs.h for details). */ + flecs::world world(argc, argv); + + world.set_target_fps(1); + + std::cout << "Application ${id} is running, press CTRL-C to exit..." << std::endl; + + /* Run systems */ + while (world.progress()) { } +} diff --git a/fggl/ecs2/flecs/test/BUILD.bazel b/fggl/ecs2/flecs/test/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..dadce385b482eb15716ad559e7b0757447f156a6 --- /dev/null +++ b/fggl/ecs2/flecs/test/BUILD.bazel @@ -0,0 +1,31 @@ +load(":bake_tests.bzl", "persuite_bake_tests") + +cc_library( + name = "test-api", + deps = ["//:flecs", "//examples:os-api", "@bake//:driver-test"], + + srcs = glob(["api/src/*.c", "api/**/*.h"]), + includes = ["api/include"], +) + +persuite_bake_tests("api", [":test-api"], glob(["api/src/*.c"], exclude=["api/src/main.c", "api/src/util.c"])) + +cc_test( + name = "collections", + deps = ["//:flecs", "@bake//:driver-test"], + + srcs = glob(["collections/src/*.c", "collections/**/*.h"]), + includes = ["collections/include"], + + timeout = "short", +) + +cc_test( + name = "cpp_api", + deps = ["//:flecs", "@bake//:driver-test"], + + srcs = glob(["cpp_api/src/*.cpp", "cpp_api/**/*.h"]), + includes = ["cpp_api/include"], + + timeout = "short", +) diff --git a/fggl/ecs2/flecs/test/api/.gitignore b/fggl/ecs2/flecs/test/api/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33560686714c750d281191605c7dd92518608fa9 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/.gitignore @@ -0,0 +1,4 @@ +.bake_cache +.DS_Store +.vscode +bin diff --git a/fggl/ecs2/flecs/test/api/include/api.h b/fggl/ecs2/flecs/test/api/include/api.h new file mode 100644 index 0000000000000000000000000000000000000000..9a5f24f56c2a6d05d22a2e546a2f1fee1e3be3e5 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/include/api.h @@ -0,0 +1,78 @@ +#ifndef API_H +#define API_H + +/* This generated file contains includes for project dependencies */ +#include <api/bake_config.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_SYS_COLUMNS (20) +#define MAX_ENTITIES (256) +#define MAX_INVOCATIONS (1024) + +typedef struct Probe { + ecs_entity_t system; + ecs_entity_t self; + ecs_entity_t event; + ecs_id_t event_id; + int32_t offset; + int32_t count; + int32_t invoked; + int32_t column_count; + int32_t term_index; + ecs_entity_t e[MAX_ENTITIES]; + ecs_entity_t c[MAX_INVOCATIONS][MAX_SYS_COLUMNS]; + ecs_entity_t s[MAX_INVOCATIONS][MAX_SYS_COLUMNS]; + void *param; +} Probe; + +typedef struct IterData { + ecs_entity_t component; + ecs_entity_t component_2; + ecs_entity_t component_3; + ecs_type_t type; + ecs_type_t type_2; + ecs_type_t type_3; + ecs_entity_t new_entities[MAX_ENTITIES]; + int32_t entity_count; +} IterData; + +typedef struct Position { + float x; + float y; +} Position; + +typedef struct Velocity { + float x; + float y; +} Velocity; + +typedef float Mass; + +typedef float Rotation; + +typedef struct Color { + float r; + float g; + float b; + float a; +} Color; + +void probe_system_w_ctx( + ecs_iter_t *it, + Probe *ctx); + +void probe_system(ecs_iter_t *it); + +void probe_has_entity(Probe *probe, ecs_entity_t e); + +void install_test_abort(); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/test/api/include/api/bake_config.h b/fggl/ecs2/flecs/test/api/include/api/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..41100f58604f7a686b68be45b182f46335f8ce7d --- /dev/null +++ b/fggl/ecs2/flecs/test/api/include/api/bake_config.h @@ -0,0 +1,29 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 API_BAKE_CONFIG_H +#define API_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> +#include <flecs_os_api_bake.h> +#ifdef __BAKE__ +#include <bake_util.h> +#endif +#include <bake_test.h> + +#endif + diff --git a/fggl/ecs2/flecs/test/api/project.json b/fggl/ecs2/flecs/test/api/project.json new file mode 100644 index 0000000000000000000000000000000000000000..dcc2407edadb943845f5dcc244c3942f96661acb --- /dev/null +++ b/fggl/ecs2/flecs/test/api/project.json @@ -0,0 +1,2152 @@ +{ + "id": "api", + "type": "application", + "value": { + "author": "Sander Mertens", + "description": "Test project for flecs", + "public": false, + "coverage": false, + "use": [ + "flecs", + "flecs.os_api.bake" + ] + }, + "test": { + "testsuites": [ + { + "id": "Entity", + "testcases": [ + "init_id", + "init_id_name", + "init_id_path", + "init_id_add_1_comp", + "init_id_add_2_comp", + "init_id_w_scope", + "init_id_name_w_scope", + "init_id_path_w_scope", + "init_id_name_1_comp", + "init_id_name_2_comp", + "init_id_name_2_comp_w_scope", + "id_add_1_comp", + "id_add_2_comp", + "id_remove_1_comp", + "id_remove_2_comp", + "init_id_path_w_sep", + "find_id_name", + "find_w_existing_id_name", + "find_id_name_w_scope", + "find_id_path", + "find_id_path_w_scope", + "find_id_name_match", + "find_id_name_match_w_scope", + "find_id_path_match", + "find_id_path_match_w_scope", + "find_id_name_mismatch", + "find_id_name_mismatch_w_scope", + "find_id_path_mismatch", + "find_id_path_mismatch_w_scope", + "find_id_add_1_comp", + "find_id_add_2_comp", + "find_id_remove_1_comp", + "find_id_remove_2_comp", + "init_w_scope_name", + "init_w_core_name", + "init_w_with", + "init_w_with_w_name", + "init_w_with_w_scope", + "init_w_with_w_name_scope", + "is_valid", + "is_recycled_valid", + "is_0_valid", + "is_junk_valid", + "is_not_alive_valid" + ] + }, { + "id": "New", + "setup": true, + "testcases": [ + "empty", + "component", + "type", + "type_of_2", + "type_w_type", + "type_w_2_types", + "type_mixed", + "tag", + "type_w_tag", + "type_w_2_tags", + "type_w_tag_mixed", + "redefine_component", + "recycle_id_empty", + "recycle_id_w_entity", + "recycle_id_w_type", + "recycle_empty_staged_delete", + "recycle_staged_delete", + "new_id", + "new_component_id", + "new_hi_component_id", + "new_component_id_skip_used", + "new_component_id_skip_to_hi_id", + "new_w_entity_0", + "create_w_explicit_id_2_worlds", + "new_w_id_0_w_with", + "new_w_id_w_with", + "new_w_type_0_w_with", + "new_w_type_w_with", + "new_w_id_w_with_w_scope", + "new_w_type_w_with_w_scope", + "new_w_id_w_with_defer", + "new_w_id_w_with_defer_w_scope", + "new_w_type_w_with_defer", + "new_w_type_w_with_defer_w_scope" + ] + }, { + "id": "New_w_Count", + "testcases": [ + "empty", + "component", + "type", + "type_of_2", + "type_w_type", + "type_w_2_types", + "type_mixed", + "tag", + "type_w_tag", + "type_w_2_tags", + "type_w_tag_mixed", + "new_w_data_1_comp", + "new_w_data_2_comp", + "new_w_data_w_tag", + "new_w_data_w_comp_and_tag", + "new_w_data_pair", + "new_w_data_pair", + "new_w_data_2_comp_1_not_set", + "new_w_on_add_on_set_monitor", + "new_w_data_override_set_comp", + "new_w_data_override_set_pair" + ] + }, { + "id": "Add", + "testcases": [ + "zero", + "component", + "component_again", + "2_components", + "2_components_again", + "2_components_overlap", + "type", + "type_of_2", + "type_w_type", + "type_w_2_types", + "type_mixed", + "type_again", + "type_overlap", + "type_again_component", + "type_overlap_component", + "component_to_nonempty", + "component_to_nonempty_again", + "component_to_nonempty_overlap", + "type_to_nonempty", + "type_to_nonempty_again", + "type_to_nonempty_overlap", + "tag", + "type_w_tag", + "type_w_2_tags", + "type_w_tag_mixed", + "add_remove", + "add_remove_same", + "add_remove_entity", + "add_remove_entity_same", + "add_2_remove", + "add_entity", + "remove_entity", + "add_0_entity", + "remove_0_entity", + "add_w_xor", + "add_same_w_xor", + "add_after_remove_xor" + ] + }, { + "id": "Switch", + "setup": true, + "testcases": [ + "get_case_empty", + "get_case_no_switch", + "get_case_unset", + "get_case_set", + "get_case_change", + "remove_case", + "remove_last", + "delete_first", + "delete_last", + "delete_first_last", + "3_entities_same_case", + "2_entities_1_change_case", + "3_entities_change_case", + "query_switch", + "query_1_case_1_type", + "query_1_case_2_types", + "query_2_cases_1_type", + "query_2_cases_2_types", + "query_after_remove", + "add_switch_in_stage", + "add_case_in_stage", + "change_case_in_stage", + "change_one_case_in_stage", + "remove_switch_in_stage", + "switch_no_match_for_case", + "empty_entity_has_case", + "zero_entity_has_case", + "add_to_entity_w_switch", + "add_pair_to_entity_w_switch", + "sort", + "recycled_tags", + "query_recycled_tags", + "single_case" + ] + }, { + "id": "EnabledComponents", + "testcases": [ + "is_component_enabled", + "is_empty_entity_disabled", + "is_0_entity_disabled", + "is_0_component_disabled", + "is_nonexist_component_disabled", + "is_enabled_component_enabled", + "is_disabled_component_enabled", + "has_enabled_component", + "is_enabled_after_add", + "is_enabled_after_remove", + "is_enabled_after_disable", + "is_disabled_after_enable", + "is_enabled_randomized", + "is_enabled_after_add_randomized", + "is_enabled_after_randomized_add_randomized", + "is_enabled_2", + "is_enabled_3", + "is_enabled_2_after_add", + "is_enabled_3_after_add", + "query_disabled", + "query_disabled_skip_initial", + "query_mod_2", + "query_mod_8", + "query_mod_64", + "query_mod_256", + "query_mod_1024", + "query_enable_mod_10", + "query_mod_2_2_bitsets", + "query_mod_8_2_bitsets", + "query_mod_64_2_bitsets", + "query_mod_256_2_bitsets", + "query_mod_1024_2_bitsets", + "query_randomized_2_bitsets", + "query_randomized_3_bitsets", + "query_randomized_4_bitsets", + "defer_enable", + "sort" + ] + }, { + "id": "Remove", + "testcases": [ + "zero", + "zero_from_nonzero", + "1_of_1", + "1_of_1_again", + "1_of_2", + "2_of_2", + "2_of_3", + "2_again", + "2_overlap", + "type_of_1_of_2", + "type_of_2_of_2", + "type_of_2_of_3", + "1_from_empty", + "type_from_empty", + "not_added" + ] + }, { + "id": "Parser", + "testcases": [ + "resolve_this", + "resolve_wildcard", + "resolve_is_a", + "0", + "component_implicit_subject", + "component_explicit_subject", + "component_explicit_subject_this", + "component_explicit_subject_this_by_name", + "component_explicit_subject_wildcard", + "pair_implicit_subject", + "pair_implicit_subject_wildcard_pred", + "pair_implicit_subject_wildcard_obj", + "pair_implicit_subject_this_pred", + "pair_implicit_subject_this_obj", + "pair_explicit_subject", + "pair_explicit_subject_this", + "pair_explicit_subject_this_by_name", + "pair_explicit_subject_wildcard_pred", + "pair_explicit_subject_wildcard_subj", + "pair_explicit_subject_wildcard_obj", + "in_component_implicit_subject", + "in_component_explicit_subject", + "in_pair_implicit_subject", + "in_pair_explicit_subject", + "inout_component_implicit_subject", + "inout_component_explicit_subject", + "inout_pair_implicit_subject", + "inout_pair_explicit_subject", + "out_component_implicit_subject", + "out_component_explicit_subject", + "out_pair_implicit_subject", + "out_pair_explicit_subject", + "component_singleton", + "this_singleton", + "component_implicit_no_subject", + "component_explicit_no_subject", + "pair_no_subject", + "variable_single_char", + "variable_multi_char", + "variable_multi_char_w_underscore", + "variable_multi_char_w_number", + "escaped_all_caps_single_char", + "escaped_all_caps_multi_char", + "component_not", + "pair_implicit_subject_not", + "pair_explicit_subject_not", + "2_component_not", + "2_component_not_no_space", + "component_optional", + "2_component_optional", + "2_component_optional_no_space", + "from_and", + "from_or", + "from_not", + "pair_implicit_subject_optional", + "pair_explicit_subject_optional", + "pred_implicit_subject_w_role", + "pred_explicit_subject_w_role", + "pred_no_subject_w_role", + "pair_implicit_subject_w_role", + "pair_explicit_subject_w_role", + "inout_role_pred_implicit_subject", + "inout_role_pred_no_subject", + "inout_role_pred_explicit_subject", + "inout_role_pair_implicit_subject", + "inout_role_pair_explicit_subject", + "2_pred_implicit_subject", + "2_pred_no_subject", + "2_pred_explicit_subject", + "2_pair_implicit_subject", + "2_pair_explicit_subject", + "2_pred_role", + "2_pair_implicit_subj_role", + "2_pair_explicit_subj_role", + "2_or_pred_implicit_subj", + "2_or_pred_explicit_subj", + "2_or_pair_implicit_subj", + "2_or_pair_explicit_subj", + "2_or_pred_inout", + "1_digit_pred_implicit_subj", + "1_digit_pred_no_subj", + "1_digit_pred_explicit_subj", + "1_digit_pair_implicit_subj", + "1_digit_pair_explicit_subj", + "pred_implicit_subject_self", + "pred_implicit_subject_superset", + "pred_implicit_subject_subset", + "pred_implicit_subject_superset_inclusive", + "pred_implicit_subject_subset_inclusive", + "pred_implicit_subject_superset_cascade", + "pred_implicit_subject_subset_cascade", + "pred_implicit_subject_superset_inclusive_cascade", + "pred_implicit_subject_subset_inclusive_cascade", + "pred_implicit_subject_implicit_superset_cascade", + "pred_implicit_subject_implicit_superset_inclusive_cascade", + "pred_implicit_subject_implicit_superset_cascade_w_rel", + "pred_implicit_subject_implicit_superset_inclusive_cascade_w_rel", + "pred_implicit_subject_superset_depth_1_digit", + "pred_implicit_subject_subset_depth_1_digit", + "pred_implicit_subject_superset_depth_2_digits", + "pred_implicit_subject_subset_depth_2_digits", + "pred_implicit_superset_min_max_depth", + "pred_implicit_superset_childof_min_max_depth", + "pred_implicit_subject_superset_childof", + "pred_implicit_subject_cascade_superset_childof", + "pred_implicit_subject_superset_cascade_childof", + "pred_implicit_subject_superset_cascade_childof_optional", + "expr_w_symbol" + ] + }, { + "id": "Plecs", + "testcases": [ + "null", + "empty", + "space", + "space_newline", + "empty_newline", + "entity", + "entity_w_entity", + "entity_w_pair", + "2_entities", + "2_entities_w_entities", + "3_entities_w_pairs" + ] + }, { + "id": "GlobalComponentIds", + "testcases": [ + "declare", + "declare_w_entity", + "declare_2_world", + "declare_tag", + "declare_tag_w_entity", + "declare_entity", + "declare_type" + ] + }, { + "id": "Hierarchies", + "setup": true, + "testcases": [ + "empty_scope", + "get_parent", + "get_parent_from_nested", + "get_parent_from_nested_2", + "get_parent_from_root", + "tree_iter_empty", + "tree_iter_1_table", + "tree_iter_2_tables", + "tree_iter_w_filter", + "path_depth_0", + "path_depth_1", + "path_depth_2", + "rel_path_from_root", + "rel_path_from_self", + "rel_path_depth_1", + "rel_path_depth_2", + "rel_path_no_match", + "path_custom_sep", + "path_custom_prefix", + "path_prefix_rel_match", + "path_prefix_rel_no_match", + "fullpath_for_core", + "path_w_number", + "lookup_depth_0", + "lookup_depth_1", + "lookup_depth_2", + "lookup_rel_0", + "lookup_rel_1", + "lookup_rel_2", + "lookup_custom_sep", + "lookup_custom_prefix", + "lookup_custom_prefix_from_root", + "lookup_self", + "lookup_in_parent_from_scope", + "lookup_in_root_from_scope", + "lookup_number", + "delete_children", + "scope_set", + "scope_set_again", + "scope_set_w_new", + "scope_set_w_new_staged", + "scope_set_w_lookup", + "scope_component", + "scope_component_no_macro", + "new_from_path_depth_0", + "new_from_path_depth_1", + "new_from_path_depth_2", + "new_from_path_existing_depth_0", + "new_from_path_existing_depth_1", + "new_from_path_existing_depth_2", + "add_path_depth_0", + "add_path_depth_1", + "add_path_depth_2", + "add_path_existing_depth_0", + "add_path_existing_depth_1", + "add_path_existing_depth_2", + "add_path_from_scope", + "add_path_from_scope_new_entity", + "new_w_child_in_root", + "delete_child", + "delete_2_children", + "delete_2_children_different_type", + "delete_tree_2_levels", + "delete_tree_3_levels", + "delete_tree_count_tables", + "delete_tree_staged", + "delete_tree_empty_table", + "delete_tree_recreate", + "delete_tree_w_onremove", + "delete_tree_w_dtor", + "get_child_count", + "get_child_count_2_tables", + "get_child_count_no_children", + "scope_iter_after_delete_tree", + "add_child_after_delete_tree", + "add_child_to_recycled_parent", + "get_type_after_recycled_parent_add", + "rematch_after_add_to_recycled_parent", + "cascade_after_recycled_parent_change", + "long_name_depth_0", + "long_name_depth_1", + "long_name_depth_2" + ] + }, { + "id": "Add_bulk", + "testcases": [ + "add_comp_from_comp_to_empty", + "add_comp_from_comp_to_existing", + "add_comp_from_tag_to_empty", + "add_comp_from_tag_to_existing", + "add_tag_from_tag_to_empty", + "add_tag_from_tag_to_existing", + "add_comp_to_more_existing", + "add_comp_to_fewer_existing", + "on_add", + "add_entity_comp", + "add_entity_tag", + "add_entity_on_add", + "add_entity_existing" + ] + }, { + "id": "Remove_bulk", + "testcases": [ + "remove_comp_from_comp_to_empty", + "remove_comp_from_comp_to_existing", + "remove_comp_from_tag_to_empty", + "remove_comp_from_tag_to_existing", + "remove_tag_from_tag_to_empty", + "remove_tag_from_tag_to_existing", + "remove_all_comp", + "remove_all_tag", + "on_remove", + "remove_entity_comp", + "remove_entity_tag", + "remove_entity_on_remove", + "bulk_remove_w_low_tag_id" + ] + }, { + "id": "Add_remove_bulk", + "testcases": [ + "add_remove_add_only", + "add_remove_remove_only", + "add_remove_both", + "add_remove_same" + ] + }, { + "id": "Has", + "testcases": [ + "zero", + "zero_from_nonzero", + "1_of_0", + "1_of_1", + "1_of_2", + "2_of_0", + "2_of_2", + "3_of_2", + "2_of_1", + "1_of_empty", + "has_in_progress", + "has_of_zero", + "owns", + "has_entity", + "has_entity_0", + "has_entity_0_component", + "has_entity_owned", + "has_entity_owned_0", + "has_entity_owned_0_component", + "has_wildcard", + "has_wildcard_pair" + ] + }, { + "id": "Count", + "testcases": [ + "count_empty", + "count_w_entity_0", + "count_1_component", + "count_2_components", + "count_3_components", + "count_2_types_2_comps" + ] + }, { + "id": "Get_component", + "setup": true, + "testcases": [ + "get_empty", + "get_1_from_1", + "get_1_from_2", + "get_2_from_2", + "get_2_from_3", + "get_1_from_2_in_progress_from_main_stage", + "get_1_from_2_add_in_progress", + "get_both_from_2_add_in_progress", + "get_both_from_2_add_remove_in_progress", + "get_childof_component" + ] + }, { + "id": "Reference", + "setup": true, + "testcases": [ + "get_ref", + "get_ref_after_add", + "get_ref_after_remove", + "get_ref_after_delete", + "get_ref_after_realloc", + "get_ref_after_realloc_w_lifecycle", + "get_ref_staged", + "get_ref_after_new_in_stage", + "get_ref_monitored", + "get_nonexisting" + ] + }, { + "id": "Delete", + "setup": true, + "testcases": [ + "delete_1", + "delete_1_again", + "delete_empty", + "delete_nonexist", + "delete_1st_of_3", + "delete_2nd_of_3", + "delete_2_of_3", + "delete_3_of_3", + "delete_w_on_remove", + "clear_1_component", + "clear_2_components", + "alive_after_delete", + "alive_after_clear", + "alive_after_staged_delete", + "alive_while_staged", + "alive_while_staged_w_delete", + "alive_while_staged_w_delete_recycled_id", + "alive_after_recycle", + "delete_recycled", + "get_alive_for_alive", + "get_alive_for_recycled", + "get_alive_for_not_alive", + "get_alive_w_generation_for_recycled_alive", + "get_alive_w_generation_for_recycled_not_alive", + "get_alive_for_0", + "get_alive_for_nonexistent", + "move_w_dtor_move", + "move_w_dtor_no_move", + "move_w_no_dtor_move", + "wrap_generation_count" + ] + }, { + "id": "OnDelete", + "testcases": [ + "on_delete_id_default", + "on_delete_id_remove", + "on_delete_id_delete", + "on_delete_relation_default", + "on_delete_relation_remove", + "on_delete_relation_delete", + "on_delete_object_default", + "on_delete_object_remove", + "on_delete_object_delete", + "on_delete_id_throw", + "on_delete_relation_throw", + "on_delete_object_throw", + "on_delete_id_remove_no_instances", + "on_delete_id_delete_no_instances", + "on_delete_id_throw_no_instances", + "on_delete_cyclic_id_default", + "on_delete_cyclic_id_remove", + "on_delete_cyclic_id_remove_both", + "on_delete_cyclic_id_delete", + "on_delete_cyclic_id_delete_both", + "on_delete_cyclic_relation_default", + "on_delete_cyclic_relation_remove", + "on_delete_cyclic_relation_remove_both", + "on_delete_cyclic_relation_delete", + "on_delete_cyclic_object_default", + "on_delete_cyclic_object_remove", + "on_delete_cyclic_object_delete", + "on_delete_remove_2_comps", + "on_delete_remove_2_comps_to_existing_table", + "on_delete_delete_recursive", + "on_delete_component_throw", + "on_delete_remove_2_relations", + "on_delete_remove_object_w_2_relations", + "on_delete_remove_object_w_5_relations", + "on_delete_remove_object_w_50_relations", + "on_delete_remove_object_w_50_relations_3_tables", + "remove_id_from_2_tables", + "remove_relation_from_2_tables", + "remove_object_from_2_tables", + "remove_id_and_relation", + "remove_id_and_relation_from_2_tables", + "stresstest_many_objects", + "stresstest_many_relations", + "stresstest_many_objects_on_delete", + "stresstest_many_relations_on_delete", + "on_delete_empty_table_w_on_remove", + "delete_table_in_on_remove_during_fini", + "delete_other_in_on_remove_during_fini", + "on_delete_remove_id_w_role", + "on_delete_merge_pair_component" + ] + }, { + "id": "Delete_w_filter", + "testcases": [ + "delete_1", + "delete_2", + "delete_1_2_types", + "delete_2_2_types", + "delete_except_1", + "delete_except_2", + "delete_with_any_of_2", + "delete_except_all_of_2", + "include_exact", + "exclude_exact", + "system_activate_test", + "skip_builtin_tables", + "delete_w_on_remove" + ] + }, { + "id": "Set", + "testcases": [ + "set_empty", + "set_nonempty", + "set_non_empty_override", + "set_again", + "set_2", + "add_set", + "set_add", + "set_add_other", + "set_remove", + "set_remove_other", + "set_remove_twice", + "set_and_new", + "set_null", + "get_mut_new", + "get_mut_existing", + "get_mut_tag_new", + "get_mut_tag_existing", + "get_mut_tag_new_w_comp", + "get_mut_tag_existing_w_comp", + "get_mut_tag_new_w_pair", + "get_mut_tag_existing_w_pair", + "modified_w_on_set", + "modified_no_component", + "get_mut_w_add_in_on_add", + "get_mut_w_remove_in_on_add", + "emplace", + "emplace_existing", + "emplace_w_move" + ] + }, { + "id": "Lookup", + "setup": true, + "testcases": [ + "lookup", + "lookup_component", + "lookup_not_found", + "lookup_child", + "lookup_w_null_name", + "get_name", + "get_name_no_name", + "get_name_from_empty", + "lookup_by_id", + "lookup_symbol_by_id", + "lookup_name_w_digit", + "lookup_symbol_w_digit", + "lookup_path_w_digit", + "set_name_of_existing", + "change_name_of_existing", + "lookup_alias", + "lookup_scoped_alias", + "define_duplicate_alias", + "lookup_null", + "lookup_symbol_null", + "lookup_this", + "lookup_wildcard", + "lookup_path_this", + "lookup_path_wildcard", + "lookup_path_this_from_scope", + "lookup_path_wildcard_from_scope" + ] + }, { + "id": "Singleton", + "testcases": [ + "set_get_singleton", + "get_mut_singleton", + "singleton_system" + ] + }, { + "id": "Clone", + "testcases": [ + "empty", + "empty_w_value", + "null", + "null_w_value", + "1_component", + "2_component", + "1_component_w_value", + "2_component_w_value", + "3_component", + "3_component_w_value", + "tag", + "tag_w_value", + "1_tag_1_component", + "1_tag_1_component_w_value" + ] + }, { + "id": "ComponentLifecycle", + "setup": true, + "testcases": [ + "ctor_on_add", + "ctor_on_new", + "dtor_on_remove", + "dtor_on_delete", + "copy_on_set", + "copy_on_override", + "copy_on_new_w_data", + "copy_on_clone", + "no_copy_on_move", + "ctor_on_bulk_add", + "dtor_on_bulk_remove", + "ctor_on_bulk_add_entity", + "dtor_on_bulk_remove_entity", + "ctor_dtor_on_bulk_add_remove", + "ctor_copy_on_snapshot", + "copy_on_snapshot", + "dtor_on_restore", + "ctor_on_tag", + "dtor_on_tag", + "copy_on_tag", + "move_on_tag", + "merge_to_different_table", + "merge_to_new_table", + "delete_in_stage", + "ctor_on_add_pair", + "ctor_on_add_pair_set_ctor_after_table", + "ctor_on_add_pair_tag", + "ctor_on_add_pair_tag_set_ctor_after_table", + "ctor_on_move_pair", + "move_on_realloc", + "move_on_dim", + "move_on_bulk_new", + "move_on_delete", + "move_dtor_on_delete", + "copy_on_override_pair", + "copy_on_override_pair_tag", + "copy_on_set_pair", + "copy_on_set_pair_tag", + "prevent_lifecycle_overwrite", + "prevent_lifecycle_overwrite_null_callbacks", + "allow_lifecycle_overwrite_equal_callbacks", + "set_lifecycle_after_trigger", + "valid_entity_in_dtor_after_delete", + "ctor_w_emplace", + "dtor_on_fini", + "valid_type_in_dtor_on_fini", + "valid_other_type_of_entity_in_dtor_on_fini", + "valid_same_type_comp_of_entity_in_dtor_on_fini", + "valid_same_type_comp_of_entity_in_dtor_on_delete_parent", + "valid_entity_bulk_remove_all_components", + "delete_in_dtor_same_type_on_fini", + "delete_in_dtor_other_type_on_fini", + "delete_self_in_dtor_on_fini", + "delete_in_dtor_same_type_on_delete_parent", + "delete_in_dtor_other_type_on_delete_parent", + "delete_self_in_dtor_on_delete_parent", + "delete_in_dtor_same_type_on_delete", + "delete_in_dtor_other_type_on_delete", + "delete_self_in_dtor_on_delete", + "on_set_after_set", + "on_set_after_new_w_data" + ] + }, { + "id": "Pipeline", + "setup": true, + "testcases": [ + "system_order_same_phase", + "system_order_same_phase_after_disable", + "system_order_same_phase_after_activate", + "system_order_different_phase", + "system_order_different_phase_after_disable", + "system_order_different_phase_after_activate", + "system_order_after_new_system_lower_id", + "system_order_after_new_system_inbetween_id", + "system_order_after_new_system_higher_id", + "merge_after_staged_out", + "merge_after_not_out", + "no_merge_after_main_out", + "no_merge_after_staged_in_out", + "merge_after_staged_out_before_owned", + "switch_pipeline", + "run_pipeline", + "get_pipeline_from_stage", + "3_systems_3_types", + "random_read_after_random_write_out_in", + "random_read_after_random_write_inout_in", + "random_read_after_random_write_out_inout", + "random_read_after_random_write_inout_inout", + "random_read_after_random_write_w_not_write", + "random_read_after_random_write_w_not_read", + "random_read_after_random_write_w_wildcard", + "random_in_after_random_inout_after_random_out" + ] + }, { + "id": "SystemMisc", + "setup": true, + "testcases": [ + "invalid_not_without_id", + "invalid_optional_without_id", + "invalid_system_without_id", + "invalid_container_without_id", + "invalid_cascade_without_id", + "invalid_entity_without_id", + "invalid_empty_without_id", + "invalid_empty_element", + "invalid_empty_element_w_space", + "invalid_empty_or", + "invalid_empty_or_w_space", + "invalid_or_w_not", + "invalid_not_w_or", + "invalid_0_w_and", + "invalid_0_w_from_system", + "invalid_0_w_from_container", + "invalid_0_w_from_cascade", + "invalid_0_w_from_entity", + "invalid_0_w_from_empty", + "invalid_or_w_empty", + "invalid_component_id", + "invalid_entity_id", + "invalid_null_string", + "invalid_empty_string", + "invalid_empty_string_w_space", + "invalid_mixed_src_modifier", + "redefine_row_system", + "system_w_or_prefab", + "system_w_or_disabled", + "system_w_or_disabled_and_prefab", + "table_columns_access", + "status_enable_after_new", + "status_enable_after_disable", + "status_disable_after_new", + "status_disable_after_disable", + "status_activate_after_new", + "status_deactivate_after_delete", + "dont_enable_after_rematch", + "ensure_single_merge", + "table_count", + "match_system", + "match_system_w_filter", + "system_initial_state", + "add_own_component", + "change_system_action", + "system_readeactivate", + "system_readeactivate_w_2_systems", + "add_to_system_in_progress", + "add_to_lazy_system_in_progress", + "redefine_null_signature", + "redefine_0_signature", + "one_named_column_of_two", + "two_named_columns_of_two", + "get_column_by_name", + "get_column_by_name_not_found", + "get_column_by_name_no_names", + "redeclare_system_same_expr", + "redeclare_system_null_expr", + "redeclare_system_0_expr", + "redeclare_system_different_expr", + "redeclare_system_null_and_expr", + "redeclare_system_expr_and_null", + "redeclare_system_expr_and_0", + "redeclare_system_0_and_expr", + "redeclare_system_0_and_null", + "redeclare_system_null_and_0", + "redeclare_system_explicit_id", + "redeclare_system_explicit_id_null_expr", + "redeclare_system_explicit_id_no_name", + "declare_different_id_same_name", + "declare_different_id_same_name_w_scope", + "rw_in_implicit_any", + "rw_in_implicit_shared", + "rw_in_implicit_from_empty", + "rw_in_implicit_from_entity", + "rw_out_explicit_any", + "rw_out_explicit_shared", + "rw_out_explicit_from_empty", + "rw_out_explicit_from_entity", + "activate_system_for_table_w_n_pairs", + "get_query", + "set_get_context", + "set_get_binding_context", + "deactivate_after_disable", + "system_w_self", + "delete_system", + "delete_pipeline_system", + "delete_system_w_ctx" + ] + }, { + "id": "Sorting", + "testcases": [ + "sort_by_component", + "sort_by_component_2_tables", + "sort_by_component_3_tables", + "sort_by_entity", + "sort_after_add", + "sort_after_remove", + "sort_after_delete", + "sort_after_set", + "sort_after_system", + "sort_after_query", + "sort_by_component_same_value_1", + "sort_by_component_same_value_2", + "sort_by_component_move_pivot", + "sort_1000_entities", + "sort_1000_entities_w_duplicates", + "sort_1000_entities_again", + "sort_1000_entities_2_types", + "sort_1500_entities_3_types", + "sort_2000_entities_4_types", + "sort_2_entities_2_types", + "sort_3_entities_3_types", + "sort_3_entities_3_types_2", + "sort_4_entities_4_types", + "sort_1000_entities_2_types_again", + "sort_1000_entities_add_type_after_sort", + "sort_shared_component", + "sort_w_tags_only", + "sort_childof_marked", + "sort_isa_marked", + "sort_relation_marked" + ] + }, { + "id": "Filter", + "testcases": [ + "filter_1_term", + "filter_2_terms", + "filter_3_terms", + "filter_3_terms_w_or", + "filter_4_terms_w_or_at_1", + "filter_w_pair_id", + "filter_w_pred_obj", + "filter_move", + "filter_copy", + "filter_w_resources_copy", + "filter_w_10_terms", + "filter_w_10_terms_move", + "filter_w_10_terms_copy", + "term_w_id", + "term_w_pair_id", + "term_w_pred_obj", + "term_w_pair_finalize_twice", + "term_w_role", + "term_w_pred_role", + "term_iter_component", + "term_iter_tag", + "term_iter_pair", + "term_iter_pair_w_rel_wildcard", + "term_iter_pair_w_obj_wildcard", + "term_iter_w_superset", + "term_iter_w_superset_childof", + "term_iter_w_superset_self", + "term_iter_w_superset_self_childof", + "term_iter_w_superset_tag", + "term_iter_w_superset_pair", + "term_iter_w_superset_pair_obj_wildcard", + "term_iter_in_stage", + "filter_iter_1_tag", + "filter_iter_2_tags", + "filter_iter_2_tags_1_not", + "filter_iter_3_tags_2_or", + "filter_iter_1_component", + "filter_iter_2_components", + "filter_iter_pair_id", + "filter_iter_2_pair_ids", + "filter_iter_pair_pred_obj", + "filter_iter_pair_2_pred_obj", + "filter_iter_null", + "filter_iter_1_not_tag", + "filter_iter_2_tags_1_optional", + "filter_iter_in_stage", + "filter_iter_10_tags", + "filter_iter_20_tags", + "filter_iter_10_components", + "filter_iter_20_components" + ] + }, { + "id": "Query", + "testcases": [ + "query_changed_after_new", + "query_changed_after_delete", + "query_changed_after_add", + "query_changed_after_remove", + "query_changed_after_set", + "query_change_after_modified", + "query_change_after_out_system", + "query_change_after_in_system", + "subquery_match_existing", + "subquery_match_new", + "subquery_inactive", + "subquery_unmatch", + "subquery_rematch", + "subquery_rematch_w_parent_optional", + "subquery_rematch_w_sub_optional", + "query_single_pairs", + "query_single_instanceof", + "query_single_childof", + "query_w_filter", + "query_optional_owned", + "query_optional_shared", + "query_optional_shared_nested", + "query_optional_any", + "query_rematch_optional_after_add", + "get_owned_tag", + "get_shared_tag", + "explicit_delete", + "get_column_size", + "orphaned_query", + "nested_orphaned_query", + "invalid_access_orphaned_query", + "stresstest_query_free", + "only_from_entity", + "only_from_singleton", + "only_not_from_entity", + "only_not_from_singleton", + "get_filter", + "group_by", + "group_by_w_ctx", + "iter_valid", + "query_optional_tag", + "query_optional_shared_tag", + "query_iter_10_tags", + "query_iter_20_tags", + "query_iter_10_components", + "query_iter_20_components" + ] + }, { + "id": "Pairs", + "testcases": [ + "type_w_one_pair", + "type_w_two_pairs", + "add_pair", + "remove_pair", + "add_tag_pair_for_tag", + "add_tag_pair_for_component", + "query_2_pairs", + "query_2_pairs_2_instances_per_type", + "query_pair_or_component", + "query_pair_or_pair", + "query_not_pair", + "override_pair", + "override_tag_pair", + "pair_w_component_query", + "on_add_pair", + "on_add_pair_tag", + "on_remove_pair", + "on_remove_pair_tag", + "on_remove_pair_on_delete", + "on_remove_pair_tag_on_delete", + "get_typeid_w_recycled_rel", + "get_typeid_w_recycled_obj", + "id_str_w_recycled_rel", + "id_str_w_recycled_obj", + "set_object_w_zero_sized_rel_comp", + "dsl_pair", + "dsl_pair_w_pred_wildcard", + "dsl_pair_w_obj_wildcard", + "dsl_pair_w_both_wildcard", + "dsl_pair_w_explicit_subj_this", + "dsl_pair_w_explicit_subj", + "api_pair", + "api_pair_w_pred_wildcard", + "api_pair_w_obj_wildcard", + "api_pair_w_both_wildcard", + "api_pair_w_explicit_subj_this", + "api_pair_w_explicit_subj", + "typeid_from_tag", + "typeid_from_component", + "typeid_from_pair", + "typeid_from_pair_w_rel_type", + "typeid_from_pair_w_obj_type", + "typeid_from_pair_w_rel_obj_type", + "typeid_from_pair_w_rel_0_obj_type", + "typeid_from_pair_w_rel_obj_0_type", + "typeid_from_pair_w_rel_0_obj_0_type", + "tag_pair_w_rel_comp", + "tag_pair_w_obj_comp", + "tag_pair_w_rel_obj_comp", + "get_tag_pair_w_rel_comp", + "get_tag_pair_w_obj_comp", + "get_tag_pair_w_rel_obj_comp", + "tag_pair_w_childof_w_comp", + "tag_pair_w_isa_w_comp", + "get_1_object", + "get_1_object_not_found", + "get_n_objects" + ] + }, { + "id": "Trigger", + "testcases": [ + "on_add_trigger_before_table", + "on_add_trigger_after_table", + "on_remove_trigger_before_table", + "on_remove_trigger_after_table", + "on_add_tag", + "on_add_component", + "on_add_wildcard", + "on_add_pair", + "on_add_pair_obj_wildcard", + "on_add_pair_pred_wildcard", + "on_add_pair_wildcard", + "on_remove_tag", + "on_remove_component", + "on_remove_wildcard", + "on_remove_pair", + "on_remove_pair_obj_wildcard", + "on_remove_pair_pred_wildcard", + "on_remove_pair_wildcard", + "on_add_remove", + "on_set_component", + "on_set_wildcard", + "on_set_pair", + "on_set_pair_w_obj_wildcard", + "on_set_pair_pred_wildcard", + "on_set_pair_wildcard", + "on_set_component_after_modified", + "un_set_component", + "un_set_wildcard", + "un_set_pair", + "un_set_pair_w_obj_wildcard", + "un_set_pair_pred_wildcard", + "un_set_pair_wildcard", + "add_twice", + "remove_twice", + "on_remove_w_clear", + "on_remove_w_delete", + "on_remove_w_world_fini", + "on_add_w_clone", + "add_in_trigger", + "remove_in_trigger", + "clear_in_trigger", + "delete_in_trigger", + "trigger_w_named_entity", + "on_remove_tree", + "set_get_context", + "set_get_binding_context", + "trigger_w_self", + "delete_trigger_w_delete_ctx", + "trigger_w_index" + ] + }, { + "id": "Observer", + "testcases": [ + "2_terms_w_on_add", + "2_terms_w_on_remove", + "2_terms_w_on_set_value", + "2_terms_w_on_remove_value", + "2_terms_w_on_add_2nd", + "2_terms_w_on_remove_2nd", + "2_pair_terms_w_on_add", + "2_pair_terms_w_on_remove", + "2_wildcard_pair_terms_w_on_add", + "2_wildcard_pair_terms_w_on_add_2_matching", + "2_wildcard_pair_terms_w_on_add_3_matching", + "2_wildcard_pair_terms_w_on_remove", + "2_terms_1_not_w_on_add", + "2_terms_1_not_w_on_remove", + "2_terms_w_on_set", + "2_terms_w_un_set", + "3_terms_2_or_on_add", + "3_terms_2_or_on_remove", + "2_terms_w_from_entity_on_add", + "2_terms_on_remove_on_clear", + "2_terms_on_remove_on_delete", + "observer_w_self", + "add_after_delete_observer", + "remove_after_delete_observer", + "delete_observer_w_ctx", + "filter_w_strings" + ] + }, { + "id": "TriggerOnAdd", + "setup": true, + "testcases": [ + "new_match_1_of_1", + "new_match_1_of_2", + "new_no_match_1", + "new_w_count_match_1_of_1", + "add_match_1_of_1", + "add_match_1_of_2", + "add_no_match_1", + "set_match_1_of_1", + "set_no_match_1", + "clone_match_1_of_1", + "clone_match_1_of_2", + "add_again_1", + "set_again_1", + "add_again_2", + "override_after_add_in_on_add", + "add_again_in_progress", + "add_in_progress_before_system_def", + "2_systems_w_table_creation", + "2_systems_w_table_creation_in_progress", + "sys_context", + "get_sys_context_from_param", + "remove_added_component_in_on_add_w_set", + "on_add_in_on_add", + "on_remove_in_on_add", + "on_set_in_on_add", + "on_add_in_on_update", + "emplace", + "add_after_delete_trigger", + "add_after_delete_wildcard_id_trigger" + ] + }, { + "id": "TriggerOnRemove", + "testcases": [ + "remove_match_1_of_1", + "remove_match_1_of_2", + "remove_no_match_1", + "delete_match_1_of_1", + "delete_match_1_of_2", + "delete_no_match_1", + "remove_watched", + "delete_watched", + "valid_entity_after_delete", + "remove_after_delete_trigger", + "remove_after_delete_wildcard_id_trigger", + "has_removed_tag_trigger_1_tag", + "has_removed_tag_trigger_2_tags" + ] + }, { + "id": "TriggerOnSet", + "testcases": [ + "set", + "set_new", + "set_again", + "clone", + "clone_w_value", + "set_and_add_system", + "on_set_after_override", + "on_set_after_override_w_new", + "on_set_after_override_w_new_w_count", + "on_set_after_override_1_of_2_overridden", + "on_set_after_snapshot_restore", + "emplace" + ] + }, { + "id": "Monitor", + "testcases": [ + "1_comp", + "2_comps", + "1_comp_1_not", + "1_parent", + "1_comp_1_parent", + "1_comp_prefab_new", + "1_comp_prefab_add" + ] + }, { + "id": "SystemOnSet", + "testcases": [ + "set_1_of_1", + "set_1_of_2", + "set_1_of_3", + "bulk_new_1", + "bulk_new_2", + "bulk_new_2_of_1", + "bulk_new_3", + "bulk_new_3_of_2", + "bulk_new_1_from_base", + "set_1_of_2_1_from_base", + "set_1_of_3_1_from_base", + "add_base", + "add_base_to_1_overridden", + "add_base_to_2_overridden", + "add_base_to_1_of_2_overridden", + "on_set_after_remove_override", + "no_set_after_remove_base", + "un_set_after_remove", + "un_set_after_remove_base", + "add_to_current_in_on_set", + "remove_from_current_in_on_set", + "remove_set_component_in_on_set", + "match_table_created_w_add_in_on_set", + "set_optional", + "set_from_nothing", + "add_null_type_in_on_set", + "add_0_entity_in_on_set", + "on_set_prefab" + ] + }, { + "id": "SystemUnSet", + "testcases": [ + "unset_1_of_1", + "unset_1_of_2", + "unset_1_of_3", + "unset_on_delete_1", + "unset_on_delete_2", + "unset_on_delete_3", + "unset_on_fini_1", + "unset_on_fini_2", + "unset_on_fini_3", + "overlapping_unset_systems", + "unset_move_to_nonempty_table", + "write_in_unset" + ] + }, { + "id": "SystemPeriodic", + "testcases": [ + "1_type_1_component", + "1_type_3_component", + "3_type_1_component", + "2_type_3_component", + "1_type_1_component_1_tag", + "2_type_1_component_1_tag", + "2_type_1_and_1_not", + "2_type_2_and_1_not", + "2_type_2_and_2_not", + "4_type_1_and_1_or", + "4_type_1_and_1_or_of_3", + "1_type_1_and_1_or", + "2_type_1_and_1_optional", + "2_type_2_and_1_optional", + "6_type_1_and_2_optional", + "ensure_optional_is_unset_column", + "ensure_optional_is_null_shared", + "match_2_systems_w_populated_table", + "on_period", + "on_period_long_delta", + "disabled", + "disabled_feature", + "disabled_nested_feature", + "two_refs", + "filter_disabled", + "match_disabled", + "match_disabled_and_enabled", + "match_prefab", + "match_prefab_and_normal", + "is_shared_on_column_not_set", + "owned_column", + "owned_not_column", + "owned_or_column", + "shared_column", + "shared_not_column", + "shared_or_column", + "container_dont_match_inheritance", + "cascade_dont_match_inheritance", + "not_from_entity", + "sys_context", + "get_sys_context_from_param", + "owned_only", + "shared_only", + "is_in_readonly", + "get_period", + "and_type", + "or_type" + ] + }, { + "id": "Timer", + "testcases": [ + "timeout", + "interval", + "shared_timeout", + "shared_interval", + "start_stop_one_shot", + "start_stop_interval", + "rate_filter", + "rate_filter_w_rate_filter_src", + "rate_filter_w_timer_src", + "rate_filter_with_empty_src", + "one_shot_timer_entity", + "interval_timer_entity", + "rate_entity", + "nested_rate_entity", + "nested_rate_entity_empty_src", + "naked_tick_entity" + ] + }, { + "id": "SystemOnDemand", + "testcases": [ + "enable_out_after_in", + "enable_in_after_out", + "enable_out_after_in_2_out_1_in", + "enable_out_after_in_1_out_2_in", + "enable_in_after_out_2_out_1_in", + "enable_in_after_out_1_out_2_in", + "disable_after_disable_in", + "disable_after_disable_in_2_out_1_in", + "disable_after_disable_in_1_out_2_in", + "table_after_out", + "table_after_out_and_in", + "table_after_out_and_in_overlapping_columns", + "1_out_system_2_in_systems", + "1_out_system_2_in_systems_different_columns", + "1_out_system_2_in_systems_overlapping_columns", + "disable_after_inactive_in_system", + "disable_after_2_inactive_in_systems", + "disable_after_2_inactive_in_systems_different_columns", + "enable_2_output_1_input_system", + "enable_2_output_1_input_system_different_columns", + "enable_2_output_1_input_system_overlapping_columns", + "out_not_column", + "trigger_on_manual", + "trigger_on_manual_not_column", + "on_demand_task_w_from_entity", + "on_demand_task_w_not_from_entity", + "enable_after_user_disable" + ] + }, { + "id": "SystemCascade", + "testcases": [ + "cascade_depth_1", + "cascade_depth_2", + "cascade_depth_2_new_syntax", + "add_after_match", + "adopt_after_match", + "rematch_w_empty_table", + "query_w_only_cascade", + "custom_relation_cascade_depth_1", + "custom_relation_cascade_depth_2", + "custom_relation_add_after_match", + "custom_relation_adopt_after_match", + "custom_relation_rematch_w_empty_table" + ] + }, { + "id": "SystemManual", + "setup": true, + "testcases": [ + "1_type_1_component", + "activate_status", + "no_automerge", + "dont_run_w_unmatching_entity_query" + ] + }, { + "id": "Tasks", + "testcases": [ + "no_components", + "one_tag", + "from_system", + "tasks_in_phases" + ] + }, { + "id": "Prefab", + "setup": true, + "testcases": [ + "new_w_prefab", + "new_w_count_prefab", + "new_w_type_w_prefab", + "add_prefab", + "add_type_w_prefab", + "remove_prefab_after_new", + "remove_prefab_after_add", + "override_component", + "override_remove_component", + "override_2_of_3_components_1_self", + "new_type_w_1_override", + "new_type_w_2_overrides", + "add_type_w_1_overrides", + "add_type_w_2_overrides", + "get_ptr_prefab", + "iterate_w_prefab_shared", + "match_entity_prefab_w_system_optional", + "prefab_in_system_expr", + "dont_match_prefab", + "new_w_count_w_override", + "override_2_components_different_size", + "ignore_prefab_parent_component", + "match_table_created_in_progress", + "prefab_w_1_child", + "prefab_w_2_children", + "prefab_w_grandchild", + "prefab_tree_1_2_1", + "prefab_w_base_w_child", + "prefab_w_child_w_base", + "prefab_w_child_w_base_w_children", + "prefab_w_child_new_w_count", + "prefab_auto_override_child_component", + "ignore_on_add", + "ignore_on_remove", + "ignore_on_set", + "on_set_on_instance", + "instantiate_in_progress", + "copy_from_prefab_in_progress", + "copy_from_prefab_first_instance_in_progress", + "ref_after_realloc", + "revalidate_ref_w_mixed_table_refs", + "no_overwrite_on_2nd_add", + "no_overwrite_on_2nd_add_in_progress", + "no_instantiate_on_2nd_add", + "no_instantiate_on_2nd_add_in_progress", + "nested_prefab_in_progress_w_count", + "nested_prefab_in_progress_w_count_set_after_override", + "get_ptr_from_prefab_from_new_table_in_progress", + "match_base", + "match_base_after_add_in_prev_phase", + "override_watched_prefab", + "rematch_twice", + "add_to_empty_base_in_system", + "dont_inherit_disabled", + "clone_after_inherit_in_on_add", + "override_from_nested", + "create_multiple_nested_w_on_set", + "create_multiple_nested_w_on_set_in_progress", + "single_on_set_on_child_w_override", + "force_owned", + "force_owned_2", + "force_owned_nested", + "force_owned_type", + "force_owned_type_w_pair", + "prefab_instanceof_hierarchy", + "override_tag", + "empty_prefab", + "instanceof_0", + "instantiate_empty_child_table", + "instantiate_emptied_child_table", + "override_2_prefabs", + "rematch_after_add_instanceof_to_parent", + "child_of_instance", + "rematch_after_prefab_delete", + "add_tag_w_low_id_to_instance", + "get_type_after_base_add", + "get_type_after_recycled_base_add", + "new_w_recycled_base", + "add_recycled_base", + "remove_recycled_base", + "get_from_recycled_base", + "override_from_recycled_base", + "remove_override_from_recycled_base", + "instantiate_tree_from_recycled_base", + "rematch_after_add_to_recycled_base", + "get_tag_from_2nd_base", + "get_component_from_2nd_base", + "get_component_from_1st_base", + "get_component_from_2nd_base_of_base", + "get_component_from_1st_base_of_base", + "get_component_from_2nd_base_prefab_base", + "get_component_from_1st_base_prefab_base", + "get_component_from_2nd_base_of_base_prefab_base", + "get_component_from_1st_base_of_base_prefab_base" + ] + }, { + "id": "System_w_FromContainer", + "setup": true, + "testcases": [ + "1_column_from_container", + "2_column_1_from_container", + "3_column_2_from_container", + "3_column_2_from_different_container", + "2_column_1_from_container_w_not", + "2_column_1_from_container_w_not_prefab", + "3_column_1_from_comtainer_1_from_container_w_not", + "2_column_1_from_container_w_or", + "select_same_from_container", + "add_component_after_match", + "add_component_after_match_and_rematch", + "add_component_after_match_unmatch", + "add_component_after_match_unmatch_match", + "add_component_after_match_2_systems", + "add_component_in_progress_after_match", + "add_component_after_match_and_rematch_w_entity_type_expr", + "add_component_after_match_and_rematch_w_entity_type_expr_in_progress", + "adopt_after_match", + "new_child_after_match", + "realloc_after_match" + ] + }, { + "id": "System_w_FromId", + "testcases": [ + "2_column_1_from_id", + "3_column_2_from_id", + "column_type" + ] + }, { + "id": "System_w_FromSystem", + "testcases": [ + "2_column_1_from_system", + "3_column_2_from_system", + "auto_add_tag" + ] + }, { + "id": "System_w_FromEntity", + "testcases": [ + "2_column_1_from_entity", + "task_from_entity", + "task_not_from_entity" + ] + }, { + "id": "World", + "setup": true, + "testcases": [ + "progress_w_0", + "progress_w_t", + "get_tick", + "entity_range_offset", + "entity_range_offset_out_of_range", + "entity_range_limit_out_of_range", + "entity_range_add_existing_in_progress", + "entity_range_add_in_range_in_progress", + "entity_range_add_out_of_range_in_progress", + "entity_range_out_of_range_check_disabled", + "entity_range_check_after_delete", + "dim", + "dim_dim_type", + "phases", + "phases_w_merging", + "phases_match_in_create", + "measure_time", + "control_fps", + "control_fps_busy_system", + "control_fps_busy_app", + "control_fps_random_system", + "control_fps_random_app", + "measure_fps_vs_actual", + "measure_delta_time_vs_actual", + "system_time_scale", + "quit", + "get_delta_time", + "get_delta_time_auto", + "recreate_world", + "recreate_world_w_component", + "no_threading", + "no_time", + "is_entity_enabled", + "get_stats", + "ensure_empty_root", + "register_alias_twice_same_entity", + "register_alias_twice_different_entity" + ] + }, { + "id": "Type", + "setup": true, + "testcases": [ + "type_of_1_tostr", + "type_of_2_tostr", + "type_of_2_tostr_no_id", + "type_redefine", + "type_has", + "type_has_not", + "zero_type_has_not", + "type_merge", + "type_merge_overlap", + "type_merge_overlap_one", + "type_add", + "type_add_empty", + "type_add_entity_again", + "type_add_out_of_order", + "type_add_existing", + "type_add_empty_existing", + "type_add_out_of_order_existing", + "type_remove", + "type_remove_empty", + "type_remove_non_existing", + "type_of_2_add", + "type_of_3_add_entity_again", + "invalid_entity_type_expression", + "invalid_container_type_expression", + "invalid_system_type_expression", + "type_from_empty_entity", + "get_type", + "get_type_from_empty", + "get_type_from_0", + "entity_from_type", + "entity_from_empty_type", + "entity_from_type_w_2_elements", + "type_from_entity", + "type_from_empty", + "type_from_0", + "type_to_expr_1_comp", + "type_to_expr_2_comp", + "type_to_expr_instanceof", + "type_to_expr_childof", + "type_to_expr_pair", + "type_to_expr_pair_w_comp", + "type_to_expr_scope", + "type_from_expr", + "type_from_expr_scope", + "type_from_expr_digit", + "type_from_expr_instanceof", + "type_from_expr_childof", + "type_from_expr_pair", + "type_from_expr_pair_w_comp", + "entity_str", + "entity_path_str", + "entity_instanceof_str", + "entity_childof_str", + "entity_pair_str", + "entity_switch_str", + "entity_case_str", + "entity_and_str", + "entity_or_str", + "entity_xor_str", + "entity_not_str", + "entity_str_small_buffer", + "role_pair_str", + "role_switch_str", + "role_case_str", + "role_and_str", + "role_or_str", + "role_xor_str", + "role_not_str", + "role_owned_str", + "role_disabled_str", + "large_type_expr", + "large_type_expr_limit" + ] + }, { + "id": "Run", + "setup": true, + "testcases": [ + "run", + "run_w_param", + "run_no_match", + "run_w_offset", + "run_w_offset_skip_1_archetype", + "run_w_offset_skip_1_archetype_plus_one", + "run_w_offset_skip_2_archetypes", + "run_w_limit_skip_1_archetype", + "run_w_limit_skip_1_archetype_minus_one", + "run_w_limit_skip_2_archetypes", + "run_w_offset_1_limit_max", + "run_w_offset_1_limit_minus_1", + "run_w_offset_2_type_limit_max", + "run_w_offset_2_type_limit_minus_1", + "run_w_limit_1_all_offsets", + "run_w_offset_out_of_bounds", + "run_w_limit_out_of_bounds", + "run_w_component_filter", + "run_w_type_filter_of_2", + "run_w_container_filter", + "run_comb_10_entities_1_type", + "run_comb_10_entities_2_types", + "run_w_interrupt", + "run_staging" + ] + }, { + "id": "MultiThread", + "setup": true, + "testcases": [ + "2_thread_1_entity", + "2_thread_2_entity", + "2_thread_5_entity", + "2_thread_10_entity", + "3_thread_1_entity", + "3_thread_2_entity", + "3_thread_5_entity", + "3_thread_10_entity", + "4_thread_1_entity", + "4_thread_2_entity", + "4_thread_5_entity", + "4_thread_10_entity", + "5_thread_1_entity", + "5_thread_2_entity", + "5_thread_5_entity", + "5_thread_10_entity", + "6_thread_1_entity", + "6_thread_2_entity", + "6_thread_5_entity", + "6_thread_10_entity", + "2_thread_test_combs_100_entity_w_next_worker", + "2_thread_test_combs_100_entity", + "3_thread_test_combs_100_entity", + "4_thread_test_combs_100_entity", + "5_thread_test_combs_100_entity", + "6_thread_test_combs_100_entity", + "2_thread_test_combs_100_entity_2_types", + "3_thread_test_combs_100_entity_2_types", + "4_thread_test_combs_100_entity_2_types", + "5_thread_test_combs_100_entity_2_types", + "6_thread_test_combs_100_entity_2_types", + "change_thread_count", + "multithread_quit", + "schedule_w_tasks", + "reactive_system", + "fini_after_set_threads" + ] + }, { + "id": "DeferredActions", + "testcases": [ + "defer_new", + "defer_bulk_new", + "defer_bulk_new_w_data", + "defer_bulk_new_w_data_pair", + "defer_bulk_new_two", + "defer_bulk_new_w_data_two", + "defer_add", + "defer_add_two", + "defer_remove", + "defer_remove_two", + "defer_set", + "defer_delete", + "defer_twice", + "defer_twice_in_progress", + "run_w_defer", + "system_in_progress_w_defer", + "defer_get_mut_no_modify", + "defer_get_mut_w_modify", + "defer_modify", + "defer_set_pair", + "defer_clear", + "defer_add_after_delete", + "defer_set_after_delete", + "defer_get_mut_after_delete", + "defer_get_mut_after_delete_2nd_to_last", + "defer_add_child_to_deleted_parent", + "recreate_deleted_entity_while_deferred", + "defer_add_to_recycled_id", + "defer_add_to_recycled_id_w_role", + "defer_add_to_recycled_relation", + "defer_add_to_recycled_object", + "defer_add_to_recycled_object_childof", + "defer_add_to_deleted_id", + "defer_add_to_deleted_id_w_role", + "defer_add_to_deleted_relation", + "defer_add_to_deleted_object", + "defer_add_to_deleted_object_childof", + "defer_delete_added_id", + "defer_delete_added_id_w_role", + "defer_delete_added_relation", + "defer_delete_added_object", + "defer_delete_added_object_childof", + "discard_add", + "discard_remove", + "discard_add_two", + "discard_remove_two", + "discard_child", + "discard_child_w_add", + "defer_return_value", + "defer_get_mut_pair", + "async_stage_add", + "async_stage_add_twice", + "async_stage_remove", + "async_stage_clear", + "async_stage_delete", + "async_stage_new", + "async_stage_no_get", + "async_stage_readonly", + "async_stage_is_async", + "register_component_while_in_progress", + "register_component_while_staged", + "register_component_while_deferred", + "defer_enable", + "defer_disable" + ] + }, { + "id": "SingleThreadStaging", + "setup": true, + "testcases": [ + "new_empty", + "new_w_component", + "new_w_type_of_2", + "new_empty_w_count", + "new_component_w_count", + "new_type_w_count", + "add_to_new_empty", + "2_add_to_new_empty", + "add_remove_same_to_new_empty", + "add_remove_2_same_to_new_empty", + "add_remove_same_to_new_w_component", + "2_add_1_remove_to_new_empty", + "2_add_1_remove_same_to_new_empty", + "clone", + "clone_w_value", + "add_to_current", + "2_add_to_current", + "remove_from_current", + "remove_2_from_current", + "add_remove_same_to_current", + "add_remove_same_existing_to_current", + "remove_add_same_to_current", + "remove_add_same_existing_to_current", + "add_remove_2_same_to_current", + "add_remove_2_same_existing_to_current", + "remove_add_2_same_to_current", + "remove_add_2_same_existing_to_current", + "add_remove_different_to_current", + "add_remove_add_same_to_current", + "2_add_1_remove_to_current", + "1_add_2_remove_to_current", + "delete_current", + "delete_even", + "delete_new_empty", + "delete_new_w_component", + "set_current", + "set_new_empty", + "set_new_w_component", + "set_existing_new_w_component", + "set_new_after_add", + "remove_after_set", + "delete_after_set", + "add_to_current_in_on_add", + "remove_from_current_in_on_add", + "remove_added_component_in_on_add", + "match_table_created_in_progress", + "match_table_created_w_new_in_progress", + "match_table_created_w_new_in_on_set", + "merge_table_w_container_added_in_progress", + "merge_table_w_container_added_on_set", + "merge_table_w_container_added_on_set_reverse", + "merge_after_tasks", + "override_after_remove_in_progress", + "get_parent_in_progress", + "merge_once", + "clear_stage_after_merge", + "get_mutable", + "get_mutable_from_main", + "get_mutable_w_add", + "on_add_after_new_type_in_progress", + "new_type_from_entity", + "existing_type_from_entity", + "new_type_add", + "existing_type_add", + "lock_table", + "recursive_lock_table", + "modify_after_lock", + "get_empty_case_from_stage", + "get_case_from_stage", + "get_object_from_stage" + ] + }, { + "id": "MultiThreadStaging", + "setup": true, + "testcases": [ + "2_threads_add_to_current", + "3_threads_add_to_current", + "4_threads_add_to_current", + "5_threads_add_to_current", + "6_threads_add_to_current", + "2_threads_on_add", + "new_w_count", + "custom_thread_auto_merge", + "custom_thread_manual_merge", + "custom_thread_partial_manual_merge" + ] + }, { + "id": "Stresstests", + "setup": true, + "testcases": [ + "create_1m_set_two_components", + "create_delete_entity_random_components", + "set_entity_random_components", + "create_delete_entity_random_components_staged", + "set_entity_random_components_staged", + "create_delete_entity_random_components_2_threads", + "set_entity_random_components_2_threads", + "create_delete_entity_random_components_6_threads", + "set_entity_random_components_6_threads", + "create_delete_entity_random_components_12_threads", + "set_entity_random_components_12_threads", + "create_2m_entities_1_comp", + "create_2m_entities_bulk_1_comp", + "add_1k_tags", + "create_1m_set_two_components" + ] + }, { + "id": "Snapshot", + "testcases": [ + "simple_snapshot", + "snapshot_after_new", + "snapshot_after_delete", + "snapshot_after_new_type", + "snapshot_after_add", + "snapshot_after_remove", + "snapshot_w_include_filter", + "snapshot_w_exclude_filter", + "snapshot_w_filter_after_new", + "snapshot_w_filter_after_delete", + "snapshot_free_empty", + "snapshot_free", + "snapshot_free_filtered", + "snapshot_free_filtered_w_dtor", + "snapshot_activate_table_w_filter", + "snapshot_copy", + "snapshot_get_ref_after_restore", + "new_after_snapshot", + "new_empty_after_snapshot", + "add_after_snapshot", + "delete_after_snapshot", + "set_after_snapshot", + "restore_recycled", + "snapshot_w_new_in_onset", + "snapshot_w_new_in_onset_in_snapshot_table", + "snapshot_from_stage" + ] + }, { + "id": "Modules", + "setup": true, + "testcases": [ + "simple_module", + "import_module_from_system", + "import_again", + "scoped_component", + "scoped_tag", + "scoped_system", + "scoped_entity", + "name_prefix_component", + "name_prefix_tag", + "name_prefix_system", + "name_prefix_entity", + "name_prefix_type", + "name_prefix_prefab", + "name_prefix_pipeline", + "name_prefix_trigger", + "name_prefix_underscore", + "lookup_by_symbol", + "import_type", + "nested_module", + "module_tag_on_namespace" + ] + }, { + "id": "DirectAccess", + "testcases": [ + "get_table_from_str", + "get_table_from_type", + "insert_record", + "insert_record_w_entity", + "table_count", + "find_column", + "get_column", + "get_empty_column", + "set_column", + "delete_column", + "delete_column_explicit", + "delete_column_w_dtor", + "copy_to", + "copy_pod_to", + "move_to", + "copy_to_no_copy", + "move_to_no_move", + "find_record_not_exists", + "get_entities_empty_table", + "get_records_empty_table", + "get_column_empty_table", + "delete_column_empty_table", + "get_record_column_empty_table", + "has_module" + ] + }, { + "id": "Internals", + "setup": true, + "testcases": [ + "deactivate_table", + "activate_table", + "activate_deactivate_table", + "activate_deactivate_reactive", + "activate_deactivate_activate_other", + "no_double_system_table_after_merge", + "recreate_deleted_table", + "create_65k_tables" + ] + }, { + "id": "Error", + "setup": true, + "testcases": [ + "abort", + "abort_w_param", + "override_abort", + "assert_true", + "assert_false", + "assert_false_w_param", + "error_codes", + "log_dbg", + "log_log", + "log_warning", + "log_error" + ] + }] + } +} diff --git a/fggl/ecs2/flecs/test/api/src/Add.c b/fggl/ecs2/flecs/test/api/src/Add.c new file mode 100644 index 0000000000000000000000000000000000000000..5de60461f72469e7ed1101fde2dddda3078639fb --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Add.c @@ -0,0 +1,689 @@ +#include <api.h> + +void Add_zero() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, 0); + test_assert(!ecs_get_type(world, e)); + + ecs_fini(world); +} + +void Add_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_component_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_2_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_2_components_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_2_components_overlap() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(!ecs_has(world, e, Mass)); + + ecs_add(world, e, Velocity); + ecs_add(world, e, Mass); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_fini(world); +} + +void Add_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_type_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_type_w_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, AND | Type_1); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type_2); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_type_w_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, Velocity); + ECS_TYPE(world, Type_3, AND | Type_1, AND | Type_2); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type_3); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_type_mixed() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, AND | Type_1, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type_2); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_type_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type_1, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type_1); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Type_1); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_type_overlap() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Velocity, Mass); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type_1); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(!ecs_has(world, e, Mass)); + + ecs_add(world, e, Type_2); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_fini(world); +} + +void Add_type_again_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_type_overlap_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_component_to_nonempty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_component_to_nonempty_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_component_to_nonempty_overlap() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_add(world, e, Position); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_type_to_nonempty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_type_to_nonempty_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_type_to_nonempty_overlap() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Velocity, Mass); + + ecs_entity_t e = ecs_new(world, Type_1); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(!ecs_has(world, e, Mass)); + + ecs_add(world, e, Type_2); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_fini(world); +} + +void Add_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add_id(world, e, Tag); + test_assert(ecs_has_entity(world, e, Tag)); + + ecs_fini(world); +} + +void Add_type_w_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_TYPE(world, Type, Tag); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Type)); + test_assert(ecs_has_entity(world, e, Tag)); + + ecs_fini(world); +} + +void Add_type_w_2_tags() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + ECS_TYPE(world, Type, Tag_1, Tag_2); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Type)); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + + ecs_fini(world); +} + +void Add_type_w_tag_mixed() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, Tag_1, Tag_2, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type); + test_assert(ecs_has(world, e, Type)); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_add_remove() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + ECS_ENTITY(world, Tag_3, 0); + ECS_TYPE(world, Type, Tag_1, Tag_2, Tag_3); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_type_t ecs_type(Tag_1) = ecs_type_from_id(world, Tag_1); + ecs_add_remove(world, e, Tag_1, Type); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(!ecs_has_entity(world, e, Tag_2)); + test_assert(!ecs_has_entity(world, e, Tag_3)); + + ecs_type_t ecs_type(Tag_2) = ecs_type_from_id(world, Tag_2); + ecs_add_remove(world, e, Tag_2, Type); + test_assert(!ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + test_assert(!ecs_has_entity(world, e, Tag_3)); + + ecs_fini(world); +} + +void Add_add_remove_same() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_type_t ecs_type(Tag_1) = ecs_type_from_id(world, Tag_1); + ecs_add_remove(world, e, Tag_1, Tag_1); + test_assert(ecs_has_entity(world, e, Tag_1)); + + ecs_fini(world); +} + +void Add_add_remove_entity() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + + ecs_entity_t e = ecs_new_w_entity(world, Tag_2); + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag_2)); + + ecs_add_remove_entity(world, e, Tag_1, Tag_2); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(!ecs_has_entity(world, e, Tag_2)); + + ecs_add_remove_entity(world, e, Tag_2, Tag_1); + test_assert(!ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + + ecs_fini(world); +} + +void Add_add_remove_entity_same() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add_remove_entity(world, e, Tag_1, Tag_1); + test_assert(ecs_has_entity(world, e, Tag_1)); + + ecs_fini(world); +} + +void Add_add_2_remove() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + ECS_ENTITY(world, Tag_3, 0); + ECS_TYPE(world, Type1, Tag_1, Tag_2, Tag_3); + ECS_TYPE(world, Type2, Tag_1, Tag_2); + ECS_TYPE(world, Type3, Tag_2, Tag_3); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add_remove(world, e, Type2, Type1); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + test_assert(!ecs_has_entity(world, e, Tag_3)); + + ecs_add_remove(world, e, Type3, Type1); + test_assert(!ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + test_assert(ecs_has_entity(world, e, Tag_3)); + + ecs_fini(world); +} + +void Add_add_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_entity_t f = ecs_new(world, 0); + test_assert(f != 0); + + ecs_add_id(world, e, f); + test_assert(ecs_has_entity(world, e, f)); + + ecs_fini(world); +} + +void Add_remove_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_entity_t f = ecs_new(world, 0); + test_assert(f != 0); + + ecs_add_id(world, e, f); + test_assert(ecs_has_entity(world, e, f)); + + ecs_remove_id(world, e, f); + test_assert(!ecs_has_entity(world, e, f)); + + ecs_fini(world); +} + +void Add_add_0_entity() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_expect_abort(); + + ecs_add_id(world, e, 0); +} + +void Add_remove_0_entity() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_expect_abort(); + + ecs_remove_id(world, e, 0); +} + +void Add_add_w_xor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + ecs_add_id(world, e, ECS_XOR | Type); + test_assert( ecs_has_entity(world, e, ECS_XOR | Type)); + test_assert( ecs_has(world, e, Position)); + + ecs_add(world, e, Velocity); + test_assert( !ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Add_add_same_w_xor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + ecs_add_id(world, e, ECS_XOR | Type); + test_assert( ecs_has_entity(world, e, ECS_XOR | Type)); + test_assert( ecs_has(world, e, Position)); + + ecs_add(world, e, Position); + test_assert( ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Add_add_after_remove_xor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + ecs_add_id(world, e, ECS_XOR | Type); + test_assert( ecs_has_entity(world, e, ECS_XOR | Type)); + test_assert( ecs_has(world, e, Position)); + + ecs_remove_id(world, e, ECS_XOR | Type); + test_assert( !ecs_has_entity(world, e, ECS_XOR | Type)); + + ecs_add(world, e, Velocity); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + diff --git a/fggl/ecs2/flecs/test/api/src/Add_bulk.c b/fggl/ecs2/flecs/test/api/src/Add_bulk.c new file mode 100644 index 0000000000000000000000000000000000000000..868228a27636a0129d39525f43fdbe420765c882 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Add_bulk.c @@ -0,0 +1,631 @@ +#include <api.h> + +void Add_bulk_add_comp_from_comp_to_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != 0); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + test_assert(ecs_get(world, e, Velocity) != NULL); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 10); + + ecs_fini(world); +} + +void Add_bulk_add_comp_from_tag_to_empty() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Tag, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Tag)); + } + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Tag)); + test_assert( ecs_has(world, e, Velocity)); + test_assert(ecs_get(world, e, Velocity) != NULL); + } + + test_int( ecs_count_entity(world, Tag), 10); + test_int( ecs_count(world, Velocity), 10); + + ecs_fini(world); +} + +void Add_bulk_add_comp_from_comp_to_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t existing[10]; + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 10); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i * 3, i * 4}); + + test_assert( ecs_has(world, e, Velocity)); + ecs_set(world, e, Velocity, {i * 5, i * 6}); + } + + ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(e != existing[i]); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + test_assert(ecs_get(world, e, Velocity) != NULL); + } + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i * 3); + test_int(p->y, i * 4); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, i * 5); + test_int(v->y, i * 6); + } + + test_int( ecs_count(world, Position), 20); + test_int( ecs_count(world, Velocity), 20); + + ecs_fini(world); +} + +void Add_bulk_add_comp_from_tag_to_existing() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Tag, Velocity); + + ecs_entity_t existing[10]; + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 10); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Velocity)); + ecs_set(world, e, Velocity, {i * 3, i * 4}); + } + + ids = ecs_bulk_new_w_entity(world, Tag, 10); + test_assert(ids != NULL); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(e != existing[i]); + test_assert( ecs_has_entity(world, e, Tag)); + } + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has_entity(world, e, Tag)); + test_assert( ecs_has(world, e, Velocity)); + test_assert(ecs_get(world, e, Velocity) != NULL); + } + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has_entity(world, e, Tag)); + test_assert( ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, i * 3); + test_int(v->y, i * 4); + } + + test_int( ecs_count_entity(world, Tag), 20); + test_int( ecs_count(world, Velocity), 20); + + ecs_fini(world); +} + +void Add_bulk_add_tag_from_tag_to_empty() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + + const ecs_entity_t *ids = ecs_bulk_new(world, Tag, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Tag)); + } + + ecs_bulk_add(world, Tag2, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Tag)); + test_assert( ecs_has(world, e, Tag2)); + } + + test_int( ecs_count_entity(world, Tag), 10); + test_int( ecs_count_entity(world, Tag2), 10); + + ecs_fini(world); +} + +void Add_bulk_add_tag_from_tag_to_existing() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + ECS_TYPE(world, Type, Tag, Tag2); + + ecs_entity_t existing[10]; + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 10); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Tag2)); + } + + ids = ecs_bulk_new_w_entity(world, Tag, 10); + test_assert(ids != NULL); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(e != existing[i]); + test_assert( ecs_has_entity(world, e, Tag)); + } + + ecs_bulk_add(world, Tag2, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has_entity(world, e, Tag)); + test_assert( ecs_has(world, e, Tag2)); + } + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has_entity(world, e, Tag)); + test_assert( ecs_has(world, e, Tag2)); + } + + test_int( ecs_count_entity(world, Tag), 20); + test_int( ecs_count_entity(world, Tag2), 20); + + ecs_fini(world); +} + +void Add_bulk_add_comp_to_more_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t existing[20]; + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 20); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 20); + + int i; + for (i = 0; i < 20; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i * 3, i * 4}); + + test_assert( ecs_has(world, e, Velocity)); + ecs_set(world, e, Velocity, {i * 5, i * 6}); + } + + ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + for (i = 0; i < 20; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i * 3); + test_int(p->y, i * 4); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, i * 5); + test_int(v->y, i * 6); + } + + test_int( ecs_count(world, Position), 30); + test_int( ecs_count(world, Velocity), 30); + + ecs_fini(world); +} + +void Add_bulk_add_comp_to_fewer_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t existing[5]; + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 5); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 5); + + int i; + for (i = 0; i < 5; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i * 3, i * 4}); + + test_assert( ecs_has(world, e, Velocity)); + ecs_set(world, e, Velocity, {i * 5, i * 6}); + } + + ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + test_assert(ecs_get(world, e, Velocity) != NULL); + } + + for (i = 0; i < 5; i ++) { + ecs_entity_t e = existing[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i * 3); + test_int(p->y, i * 4); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, i * 5); + test_int(v->y, i * 6); + } + + test_int( ecs_count(world, Position), 15); + test_int( ecs_count(world, Velocity), 15); + + ecs_fini(world); +} + +void AddVelocity(ecs_iter_t *it) { + probe_system(it); +} + +void Add_bulk_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TRIGGER(world, AddVelocity, EcsOnAdd, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 10); + test_int(ctx.system, AddVelocity); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + int i; + for (i = 0; i < 10; i ++) { + test_int(ctx.e[i], ids[i]); + } + + test_int(ctx.c[0][0], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Add_bulk_add_entity_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add_entity(world, ecs_id(Velocity), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + test_assert(ecs_get(world, e, Velocity) != NULL); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 10); + + ecs_fini(world); +} + +void Add_bulk_add_entity_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add_entity(world, Tag, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has_entity(world, e, Tag)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count_entity(world, Tag), 10); + + ecs_fini(world); +} + +void Add_bulk_add_entity_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TRIGGER(world, AddVelocity, EcsOnAdd, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + ecs_bulk_add_entity(world, ecs_id(Velocity), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 10); + test_int(ctx.system, AddVelocity); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + int i; + for (i = 0; i < 10; i ++) { + test_int(ctx.e[i], ids[i]); + } + + test_int(ctx.c[0][0], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Add_bulk_add_entity_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + ecs_set(world, e, Position, {i, i * 2}); + } + + ecs_bulk_add_entity(world, ecs_id(Position), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 10); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Add_remove_bulk.c b/fggl/ecs2/flecs/test/api/src/Add_remove_bulk.c new file mode 100644 index 0000000000000000000000000000000000000000..6b13cd988e473246436e801144379a38c2ab93af --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Add_remove_bulk.c @@ -0,0 +1,160 @@ +#include <api.h> + +void Add_remove_bulk_add_remove_add_only() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_add_remove(world, Velocity, 0, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + test_assert(ecs_get(world, ids[i], Velocity) != NULL); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 10); + + ecs_fini(world); +} + +void Add_remove_bulk_add_remove_remove_only() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_add_remove(world, 0, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Add_remove_bulk_add_remove_both() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_add_remove(world, Mass, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + test_assert( ecs_has(world, ids[i], Mass)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + test_assert(ecs_get(world, ids[i], Mass) != NULL); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Add_remove_bulk_add_remove_same() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + ecs_set(world, ids[i], Velocity, {i * 3, i * 4}); + } + + ecs_bulk_add_remove(world, Velocity, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_assert(v != NULL); + test_int(v->x, i * 3); + test_int(v->y, i * 4); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 10); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Clone.c b/fggl/ecs2/flecs/test/api/src/Clone.c new file mode 100644 index 0000000000000000000000000000000000000000..a8f9b27553694ff57d0c52431003f5c9e91cd3cd --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Clone.c @@ -0,0 +1,352 @@ +#include <api.h> + +void Clone_empty() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(!ecs_get_type(world, e1)); + test_assert(!ecs_get_type(world, e2)); + + ecs_fini(world); +} + +void Clone_empty_w_value() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(!ecs_get_type(world, e1)); + test_assert(!ecs_get_type(world, e2)); + + ecs_fini(world); +} + +void Clone_null() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + ecs_clone(world, 0, 0, false); +} + +void Clone_null_w_value() { + install_test_abort(); + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + ecs_clone(world, 0, 0, true); +} + +void Clone_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_get(world, e1, Position) != + ecs_get(world, e2, Position)); + + ecs_fini(world); +} + +void Clone_2_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_get(world, e1, Position) != + ecs_get(world, e2, Position)); + + test_assert(ecs_has(world, e1, Velocity)); + test_assert(ecs_has(world, e2, Velocity)); + test_assert(ecs_get(world, e1, Velocity) != + ecs_get(world, e2, Velocity)); + + ecs_fini(world); +} + +void Clone_3_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type, Position, Velocity, Mass); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_get(world, e1, Position) != + ecs_get(world, e2, Position)); + + test_assert(ecs_has(world, e1, Velocity)); + test_assert(ecs_has(world, e2, Velocity)); + test_assert(ecs_get(world, e1, Velocity) != + ecs_get(world, e2, Velocity)); + + test_assert(ecs_has(world, e1, Mass)); + test_assert(ecs_has(world, e2, Mass)); + test_assert(ecs_get(world, e1, Mass) != + ecs_get(world, e2, Mass)); + + ecs_fini(world); +} + +void Clone_1_component_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_set(world, e1, Position, {10, 20}); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + + const Position *p_1 = ecs_get(world, e1, Position); + test_assert(p_1 != NULL); + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Position *p_2 = ecs_get(world, e2, Position); + test_assert(p_2 != NULL); + test_assert(p_1 != p_2); + test_int(p_2->x, 10); + test_int(p_2->y, 20); + + ecs_fini(world); +} + +void Clone_2_component_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_set(world, e1, Position, {10, 20}); + ecs_set(world, e1, Velocity, {30, 40}); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_has(world, e1, Velocity)); + test_assert(ecs_has(world, e2, Velocity)); + + const Position *p_1 = ecs_get(world, e1, Position); + test_assert(p_1 != NULL); + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Position *p_2 = ecs_get(world, e2, Position); + test_assert(p_2 != NULL); + test_assert(p_1 != p_2); + test_int(p_2->x, 10); + test_int(p_2->y, 20); + + const Velocity *v_1 = ecs_get(world, e1, Velocity); + test_assert(v_1 != NULL); + test_int(v_1->x, 30); + test_int(v_1->y, 40); + + const Velocity *v_2 = ecs_get(world, e2, Velocity); + test_assert(v_2 != NULL); + test_assert(v_1 != v_2); + test_int(v_2->x, 30); + test_int(v_2->y, 40); + + ecs_fini(world); +} + +void Clone_3_component_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_set(world, e1, Position, {10, 20}); + ecs_set(world, e1, Velocity, {30, 40}); + ecs_set(world, e1, Mass, {50}); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_has(world, e1, Velocity)); + test_assert(ecs_has(world, e2, Velocity)); + test_assert(ecs_has(world, e1, Mass)); + test_assert(ecs_has(world, e2, Mass)); + + const Position *p_1 = ecs_get(world, e1, Position); + test_assert(p_1 != NULL); + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Position *p_2 = ecs_get(world, e2, Position); + test_assert(p_2 != NULL); + test_assert(p_1 != p_2); + test_int(p_2->x, 10); + test_int(p_2->y, 20); + + const Velocity *v_1 = ecs_get(world, e1, Velocity); + test_assert(v_1 != NULL); + test_int(v_1->x, 30); + test_int(v_1->y, 40); + + const Velocity *v_2 = ecs_get(world, e2, Velocity); + test_assert(v_2 != NULL); + test_assert(v_1 != v_2); + test_int(v_2->x, 30); + test_int(v_2->y, 40); + + const Mass *m_1 = ecs_get(world, e1, Mass); + test_assert(m_1 != NULL); + test_int(*m_1, 50); + + const Mass *m_2 = ecs_get(world, e2, Mass); + test_assert(m_2 != NULL); + test_assert(m_1 != m_2); + test_int(*m_2, 50); + + ecs_fini(world); +} + +void Clone_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + + ecs_entity_t e1 = ecs_new_w_entity(world, Tag); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has_entity(world, e1, Tag)); + test_assert(ecs_has_entity(world, e2, Tag)); + + ecs_fini(world); +} + +void Clone_tag_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + + ecs_entity_t e1 = ecs_new_w_entity(world, Tag); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has_entity(world, e1, Tag)); + test_assert(ecs_has_entity(world, e2, Tag)); + + ecs_fini(world); +} + +void Clone_1_tag_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, e1, Position, Tag); + + ecs_set(world, e1, Position, {10, 20}); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has_entity(world, e1, Tag)); + test_assert(ecs_has_entity(world, e2, Tag)); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + + ecs_fini(world); +} + +void Clone_1_tag_1_component_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, e1, Position, Tag); + + ecs_set(world, e1, Position, {10, 20}); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + test_assert(e2 != 0); + test_assert(e1 != e2); + + test_assert(ecs_has_entity(world, e1, Tag)); + test_assert(ecs_has_entity(world, e2, Tag)); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + + const Position *p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/ComponentLifecycle.c b/fggl/ecs2/flecs/test/api/src/ComponentLifecycle.c new file mode 100644 index 0000000000000000000000000000000000000000..7ab4b375c5de328d747cee100889a0655cf1dc39 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/ComponentLifecycle.c @@ -0,0 +1,2576 @@ +#include <api.h> + +void ComponentLifecycle_setup() { + ecs_tracing_enable(-3); +} + +typedef struct xtor_ctx { + ecs_world_t *world; + ecs_entity_t component; + ecs_entity_t entity; + size_t size; + int32_t count; + int32_t invoked; +} xtor_ctx; + +typedef struct copy_ctx { + ecs_world_t *world; + ecs_entity_t component; + ecs_entity_t entity; + ecs_entity_t src_entity; + size_t size; + int32_t count; + int32_t invoked; +} copy_ctx; + +typedef struct cl_ctx { + xtor_ctx ctor; + xtor_ctx dtor; + copy_ctx copy; + copy_ctx move; +} cl_ctx; + +static +void comp_ctor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + cl_ctx *data = ctx; + data->ctor.world = world; + data->ctor.component = component; + data->ctor.entity = entity_ptr[0], + data->ctor.size = size; + data->ctor.count += count; + data->ctor.invoked ++; + + Position *p = ptr; + int i; + for (i = 0; i < count; i ++) { + p[i].x = 10; + p[i].y = 20; + } +} + +static +void comp_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + cl_ctx *data = ctx; + data->dtor.world = world; + data->dtor.component = component; + data->dtor.entity = entity_ptr[0], + data->dtor.size = size; + data->dtor.count += count; + data->dtor.invoked ++; +} + +static +void comp_copy( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + const void *src_ptr, + size_t size, + int32_t count, + void *ctx) +{ + cl_ctx *data = ctx; + data->copy.world = world; + data->copy.component = component; + data->copy.entity = dst_entity[0], + data->copy.src_entity = src_entity[0], + data->copy.size = size; + data->copy.count += count; + data->copy.invoked ++; + + memcpy(dst_ptr, src_ptr, size * count); +} + +static +void comp_move( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx) +{ + cl_ctx *data = ctx; + data->move.world = world; + data->move.component = component; + data->move.entity = dst_entity[0], + data->move.src_entity = src_entity[0], + data->move.size = size; + data->move.count = count; + data->move.invoked ++; + + memcpy(dst_ptr, src_ptr, size * count); +} + +void ComponentLifecycle_ctor_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + + ecs_add(world, e, Position); + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .dtor = comp_dtor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.dtor.invoked, 0); + + ecs_remove(world, e, Position); + test_assert(ctx.dtor.invoked != 0); + test_assert(ctx.dtor.world == world); + test_int(ctx.dtor.component, ecs_id(Position)); + test_int(ctx.dtor.entity, e); + test_int(ctx.dtor.size, sizeof(Position)); + test_int(ctx.dtor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .dtor = comp_dtor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.dtor.invoked, 0); + + ecs_delete(world, e); + test_assert(ctx.dtor.invoked != 0); + test_assert(ctx.dtor.world == world); + test_int(ctx.dtor.component, ecs_id(Position)); + test_int(ctx.dtor.entity, e); + test_int(ctx.dtor.size, sizeof(Position)); + test_int(ctx.dtor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.copy.invoked, 0); + + ecs_set(world, e, Position, {0, 0}); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, e); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t base = ecs_new(world, Position); + test_int(ctx.copy.invoked, 0); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_int(ctx.copy.invoked, 0); + + ecs_add(world, e, Position); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, e); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_new_w_data() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 2, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position) + }, + .count = 1 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40} + } + }); + + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, ids[0]); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 2); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_clone() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(ctx.copy.invoked != 0); + memset(&ctx, 0, sizeof(ctx)); + + ecs_entity_t clone = ecs_clone(world, 0, e, true); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, clone); + test_int(ctx.copy.src_entity, e); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_no_copy_on_move() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(ctx.copy.invoked != 0); + memset(&ctx, 0, sizeof(ctx)); + + ecs_add(world, e, Velocity); + test_int(ctx.copy.invoked, 0); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_bulk_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_int(ctx.ctor.invoked, 0); + + ecs_bulk_add(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Velocity)); + test_int(ctx.ctor.entity, ids[0]); + test_int(ctx.ctor.size, sizeof(Velocity)); + test_int(ctx.ctor.count, 10); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + } + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_bulk_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .dtor = comp_dtor, + .ctx = &ctx + }); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_int(ctx.dtor.invoked, 0); + + ecs_bulk_remove(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(ctx.dtor.invoked != 0); + test_assert(ctx.dtor.world == world); + test_int(ctx.dtor.component, ecs_id(Velocity)); + test_int(ctx.dtor.entity, ids[0]); + test_int(ctx.dtor.size, sizeof(Velocity)); + test_int(ctx.dtor.count, 10); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_bulk_add_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_int(ctx.ctor.invoked, 0); + + ecs_bulk_add_entity(world, ecs_id(Velocity), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Velocity)); + test_int(ctx.ctor.entity, ids[0]); + test_int(ctx.ctor.size, sizeof(Velocity)); + test_int(ctx.ctor.count, 10); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + } + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_bulk_remove_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .dtor = comp_dtor, + .ctx = &ctx + }); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_int(ctx.dtor.invoked, 0); + + ecs_bulk_remove_entity(world, ecs_id(Velocity), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(ctx.dtor.invoked != 0); + test_assert(ctx.dtor.world == world); + test_int(ctx.dtor.component, ecs_id(Velocity)); + test_int(ctx.dtor.entity, ids[0]); + test_int(ctx.dtor.size, sizeof(Velocity)); + test_int(ctx.dtor.count, 10); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_dtor_on_bulk_add_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Mass); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + cl_ctx ctx_ctor = { { 0 } }; + cl_ctx ctx_dtor = { { 0 } }; + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx_ctor + }); + + ecs_set(world, ecs_id(Mass), EcsComponentLifecycle, { + .dtor = comp_dtor, + .ctx = &ctx_dtor + }); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Mass)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_int(ctx_ctor.ctor.invoked, 0); + test_int(ctx_dtor.dtor.invoked, 0); + + ecs_bulk_add_remove(world, Velocity, Mass, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(ctx_ctor.ctor.invoked != 0); + test_assert(ctx_ctor.ctor.world == world); + test_int(ctx_ctor.ctor.component, ecs_id(Velocity)); + test_int(ctx_ctor.ctor.entity, ids[0]); + test_int(ctx_ctor.ctor.size, sizeof(Velocity)); + test_int(ctx_ctor.ctor.count, 10); + + test_assert(ctx_dtor.dtor.invoked != 0); + test_assert(ctx_dtor.dtor.world == world); + test_int(ctx_dtor.dtor.component, ecs_id(Mass)); + test_int(ctx_dtor.dtor.entity, ids[0]); + test_int(ctx_dtor.dtor.size, sizeof(Mass)); + test_int(ctx_dtor.dtor.count, 10); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + test_assert( !ecs_has(world, ids[i], Mass)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + } + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + int32_t i; + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, ids[i - 1]); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 10); + + ctx = (cl_ctx){ { 0 } }; + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, ids[0]); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 10); + + ecs_snapshot_restore(world, s); + + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_copy_on_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .copy = comp_copy, + .ctx = &ctx + }); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + int32_t i; + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, ids[i - 1]); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 10); + + ctx = (cl_ctx){ { 0 } }; + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, ids[0]); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 10); + + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, ids[0]); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 10); + + ecs_snapshot_restore(world, s); + + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_restore() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .dtor = comp_dtor, + .ctx = &ctx + }); + + const ecs_entity_t *temp_ids = ecs_bulk_new(world, Position, 10); + test_assert(temp_ids != NULL); + ecs_entity_t ids[10]; + memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 10); + + int32_t i; + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + test_int(ctx.dtor.invoked, 0); + + ctx = (cl_ctx){ { 0 } }; + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + test_int(ctx.dtor.invoked, 0); + + /* Delete one entity, so we have more confidence we're destructing the right + * entities */ + ecs_delete(world, ids[0]); + + test_assert(ctx.dtor.invoked != 0); + ctx = (cl_ctx){ { 0 } }; + + ecs_snapshot_restore(world, s); + + test_assert(ctx.dtor.invoked != 0); + test_assert(ctx.dtor.world == world); + test_int(ctx.dtor.component, ecs_id(Position)); + test_int(ctx.dtor.size, sizeof(Position)); + test_int(ctx.dtor.count, 9); + + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + + test_int(p->x, i); + test_int(p->y, i * 2); + } + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_tag() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + test_expect_abort(); + + ecs_set(world, Tag, EcsComponentLifecycle, { + .ctor = comp_ctor + }); + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_tag() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + test_expect_abort(); + + ecs_set(world, Tag, EcsComponentLifecycle, { + .dtor = comp_dtor + }); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_tag() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + test_expect_abort(); + + ecs_set(world, Tag, EcsComponentLifecycle, { + .copy = comp_copy + }); + + ecs_fini(world); +} + +void ComponentLifecycle_move_on_tag() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + test_expect_abort(); + + ecs_set(world, Tag, EcsComponentLifecycle, { + .move = comp_move + }); + + ecs_fini(world); +} + +/* Position */ + +static int ctor_position = 0; +static +ECS_CTOR(Position, ptr, { + ptr->x = 0; + ptr->y = 0; + ctor_position ++; +}); + +static int dtor_position = 0; +static +ECS_DTOR(Position, ptr, { + dtor_position ++; +}); + +static int copy_position = 0; +static +ECS_COPY(Position, dst, src, { + copy_position ++; +}); + +static int move_position = 0; +static +ECS_MOVE(Position, dst, src, { + move_position ++; +}); + +/* Velocity */ + +static int ctor_velocity = 0; +static +ECS_CTOR(Velocity, ptr, { + ctor_velocity ++; +}); + +static int dtor_velocity = 0; +static +ECS_DTOR(Velocity, ptr, { + dtor_velocity ++; +}); + +static int copy_velocity = 0; +static +ECS_COPY(Velocity, dst, src, { + copy_velocity ++; +}); + +static int move_velocity = 0; +static +ECS_MOVE(Velocity, dst, src, { + move_velocity ++; +}); + +/* Mass */ + +static int ctor_mass = 0; +static +ECS_CTOR(Mass, ptr, { + ctor_mass ++; +}); + +static int dtor_mass = 0; +static +ECS_DTOR(Mass, ptr, { + dtor_mass ++; +}); + +static int copy_mass = 0; +static +ECS_COPY(Mass, dst, src, { + copy_mass ++; +}); + +static int move_mass = 0; +static +ECS_MOVE(Mass, dst, src, { + move_mass ++; +}); + +/* Rotation */ + +static int ctor_rotation = 0; +static +ECS_CTOR(Rotation, ptr, { + ctor_rotation ++; +}); + +static int dtor_rotation = 0; +static +ECS_DTOR(Rotation, ptr, { + dtor_rotation ++; +}); + +static int copy_rotation = 0; +static +ECS_COPY(Rotation, dst, src, { + copy_rotation ++; +}); + +static int move_rotation = 0; +static +ECS_MOVE(Rotation, dst, src, { + move_rotation ++; +}); + +void ComponentLifecycle_merge_to_different_table() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e, Position, Velocity, Rotation); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .ctor = ecs_ctor(Velocity), + .dtor = ecs_dtor(Velocity), + .copy = ecs_copy(Velocity), + .move = ecs_move(Velocity) + }); + + ecs_set(world, ecs_id(Mass), EcsComponentLifecycle, { + .ctor = ecs_ctor(Mass), + .dtor = ecs_dtor(Mass), + .copy = ecs_copy(Mass), + .move = ecs_move(Mass) + }); + + ecs_set(world, ecs_id(Rotation), EcsComponentLifecycle, { + .ctor = ecs_ctor(Rotation), + .dtor = ecs_dtor(Rotation), + .copy = ecs_copy(Rotation), + .move = ecs_move(Rotation) + }); + + ecs_defer_begin(world); + + ecs_remove(world, e, Position); + test_int(ctor_position, 0); + test_int(dtor_position, 0); + test_int(copy_position, 0); + test_int(move_position, 0); + + test_int(ctor_velocity, 0); + test_int(dtor_velocity, 0); + test_int(copy_velocity, 0); + test_int(move_velocity, 0); + + test_int(ctor_rotation, 0); + test_int(dtor_rotation, 0); + test_int(copy_rotation, 0); + test_int(move_rotation, 0); + + ecs_add(world, e, Mass); + test_int(ctor_mass, 0); + + test_int(ctor_velocity, 0); + test_int(dtor_velocity, 0); + test_int(copy_velocity, 0); + test_int(move_velocity, 0); + + test_int(ctor_rotation, 0); + test_int(dtor_rotation, 0); + test_int(copy_rotation, 0); + test_int(move_rotation, 0); + + ecs_remove(world, e, Rotation); + test_int(ctor_rotation, 0); + test_int(dtor_rotation, 0); + test_int(copy_rotation, 0); + test_int(move_rotation, 0); + + test_int(ctor_velocity, 0); + test_int(dtor_velocity, 0); + test_int(copy_velocity, 0); + test_int(move_velocity, 0); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + test_assert(!ecs_has(world, e, Rotation)); + + test_int(ctor_position, 0); + test_int(dtor_position, 1); // removed first, no moves + test_int(copy_position, 0); + test_int(move_position, 0); + + test_int(ctor_velocity, 3); // got moved 3 times + test_int(dtor_velocity, 3); + test_int(copy_velocity, 0); + test_int(move_velocity, 3); + + test_int(ctor_rotation, 2); // got moved 2 times, then removed + test_int(dtor_rotation, 3); + test_int(copy_rotation, 0); + test_int(move_rotation, 2); + + test_int(ctor_mass, 2); // got added, moved once + test_int(dtor_mass, 1); + test_int(copy_mass, 0); + test_int(move_mass, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_merge_to_new_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + ecs_defer_begin(world); + + ecs_add(world, e, Position); + + ecs_defer_end(world); + + test_int(ctor_position, 1); + test_int(move_position, 0); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e, Position, Velocity, Mass); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .ctor = ecs_ctor(Velocity), + .dtor = ecs_dtor(Velocity), + .copy = ecs_copy(Velocity), + .move = ecs_move(Velocity) + }); + + ecs_set(world, ecs_id(Mass), EcsComponentLifecycle, { + .ctor = ecs_ctor(Mass), + .dtor = ecs_dtor(Mass), + .copy = ecs_copy(Mass), + .move = ecs_move(Mass) + }); + + ecs_defer_begin(world); + + /* None of the components should be destructed while in the stage as they + * were never copied to the stage */ + ecs_delete(world, e); + test_int(dtor_position, 0); + test_int(dtor_velocity, 0); + test_int(dtor_mass, 0); + + ecs_defer_end(world); + + test_assert(ecs_exists(world, e)); + test_assert(!ecs_is_alive(world, e)); + + /* Position is destroyed in main stage */ + test_int(ctor_position, 0); + test_int(dtor_position, 1); + test_int(copy_position, 0); + test_int(move_position, 0); + + /* Velocity is destroyed in main stage */ + test_int(ctor_velocity, 0); + test_int(dtor_velocity, 1); + test_int(copy_velocity, 0); + test_int(move_velocity, 0); + + /* Mass is destroyed in main stage */ + test_int(ctor_mass, 0); + test_int(dtor_mass, 1); + test_int(copy_mass, 0); + test_int(move_mass, 0); + + ecs_fini(world); +} + +typedef struct Pair { + float value_1; + float value_2; +} Pair; + +void ComponentLifecycle_ctor_on_add_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Pair), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + + ecs_add_pair(world, e, ecs_id(Pair), ecs_id(Position)); + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Pair)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Pair)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_add_pair_set_ctor_after_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + ecs_add_pair(world, e, ecs_id(Pair), ecs_id(Position)); + + /* Remove pair so we can add it again after registering the ctor */ + ecs_remove_pair(world, e, ecs_id(Pair), ecs_id(Position)); + + /* Register component after table has been created */ + ecs_set(world, ecs_id(Pair), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + /* Re-add */ + ecs_add_pair(world, e, ecs_id(Pair), ecs_id(Position)); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Pair)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Pair)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_add_pair_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + + ecs_add_pair(world, e, Pair, ecs_id(Position)); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_add_pair_tag_set_ctor_after_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + ecs_add_pair(world, e, Pair, ecs_id(Position)); + + /* Remove pair so we can add it again after registering the ctor */ + ecs_remove_pair(world, e, Pair, ecs_id(Position)); + + /* Register component after table has been created */ + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + /* Re-add */ + ecs_add_pair(world, e, Pair, ecs_id(Position)); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_on_move_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Pair), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + /* Create entity in existing table */ + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.ctor.invoked, 0); + + /* Add pair to existing table */ + ecs_add_pair(world, e, ecs_id(Pair), ecs_id(Position)); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Pair)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Pair)); + test_int(ctx.ctor.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_move_on_realloc() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .move = comp_move, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + + ecs_add(world, e, Position); + test_assert(ctx.ctor.invoked != 0); + test_int(ctx.move.invoked, 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ctx = (cl_ctx){ { 0 } }; + + /* Trigger realloc & move */ + ecs_new(world, Position); + ecs_new(world, Position); + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.move.invoked != 0); + + ecs_fini(world); +} + +void ComponentLifecycle_move_on_dim() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .move = comp_move, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + + ecs_add(world, e, Position); + test_assert(ctx.ctor.invoked != 0); + test_int(ctx.move.invoked, 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ctx = (cl_ctx){ { 0 } }; + + /* Trigger realloc & move */ + ecs_dim_type(world, ecs_type(Position), 1000); + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.move.invoked != 0); + + ecs_fini(world); +} + +void ComponentLifecycle_move_on_bulk_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .move = comp_move, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.ctor.invoked, 0); + + ecs_add(world, e, Position); + test_assert(ctx.ctor.invoked != 0); + test_int(ctx.move.invoked, 0); + test_assert(ctx.ctor.world == world); + test_int(ctx.ctor.component, ecs_id(Position)); + test_int(ctx.ctor.entity, e); + test_int(ctx.ctor.size, sizeof(Position)); + test_int(ctx.ctor.count, 1); + + ctx = (cl_ctx){ { 0 } }; + + /* Trigger realloc & move */ + ecs_bulk_new(world, Position, 1000); + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.move.invoked != 0); + + ecs_fini(world); +} + +void ComponentLifecycle_move_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .move = comp_move, + .ctx = &ctx + }); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(ctx.ctor.invoked != 0); + + ctx = (cl_ctx){ { 0 } }; + + ecs_delete(world, e1); /* Should trigger move of e2 */ + + test_int(ctx.ctor.invoked, 0); + test_assert(ctx.move.invoked != 0); + test_assert(ctx.move.world == world); + test_int(ctx.move.component, ecs_id(Position)); + test_int(ctx.move.entity, e2); + test_int(ctx.move.size, sizeof(Position)); + test_int(ctx.move.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_move_dtor_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .dtor = comp_dtor, + .move = comp_move, + .ctx = &ctx + }); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + + ctx = (cl_ctx){ { 0 } }; + + ecs_delete(world, e1); /* Should trigger move of e2 */ + + test_assert(ctx.dtor.invoked != 0); + test_assert(ctx.move.invoked != 0); + test_assert(ctx.move.world == world); + + test_int(ctx.dtor.component, ecs_id(Position)); + test_int(ctx.dtor.entity, e1); + test_int(ctx.dtor.size, sizeof(Position)); + test_int(ctx.dtor.count, 1); + + test_int(ctx.move.component, ecs_id(Position)); + test_int(ctx.move.entity, e2); + test_int(ctx.move.size, sizeof(Position)); + test_int(ctx.move.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_override_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Pair), EcsComponentLifecycle, { + .ctor = comp_ctor, + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t base = ecs_new(world, 0); + ecs_add_pair(world, base, ecs_id(Pair), ecs_id(Position)); + test_assert(ctx.ctor.invoked != 0); + test_int(ctx.copy.invoked, 0); + + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + test_assert(ctx.ctor.invoked != 0); /* No change */ + test_int(ctx.copy.invoked, 0); + + ctx = (cl_ctx){ { 0 } }; + + /* Override */ + ecs_add_pair(world, instance, ecs_id(Pair), ecs_id(Position)); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Pair)); + test_int(ctx.copy.entity, instance); + test_int(ctx.copy.size, sizeof(Pair)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_override_pair_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t base = ecs_new(world, 0); + ecs_add_pair(world, base, Pair, ecs_id(Position)); + test_assert(ctx.ctor.invoked != 0); + test_int(ctx.copy.invoked, 0); + + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + test_assert(ctx.ctor.invoked != 0); /* No change */ + test_int(ctx.copy.invoked, 0); + + ctx = (cl_ctx){ { 0 } }; + + /* Override */ + ecs_add_pair(world, instance, Pair, ecs_id(Position)); + + test_assert(ctx.ctor.invoked != 0); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, instance); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_set_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Pair), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.copy.invoked, 0); + + ecs_set_pair(world, e, Pair, ecs_id(Position), {0, 0}); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Pair)); + test_int(ctx.copy.entity, e); + test_int(ctx.copy.size, sizeof(Pair)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_copy_on_set_pair_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .copy = comp_copy, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.copy.invoked, 0); + + ecs_set_pair_object(world, e, Pair, Position, {0, 0}); + test_assert(ctx.copy.invoked != 0); + test_assert(ctx.copy.world == world); + test_int(ctx.copy.component, ecs_id(Position)); + test_int(ctx.copy.entity, e); + test_int(ctx.copy.size, sizeof(Position)); + test_int(ctx.copy.count, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_prevent_lifecycle_overwrite() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor + }); + + /* Should trigger assert */ + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position) + }); + + ecs_fini(world); +} + +void ComponentLifecycle_prevent_lifecycle_overwrite_null_callbacks() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + NULL + }); + + /* Should trigger assert */ + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position) + }); + + ecs_fini(world); +} + +void ComponentLifecycle_allow_lifecycle_overwrite_equal_callbacks() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position) + }); + + /* Same actions, should be ok */ + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position) + }); + + ecs_new(world, Position); + + test_int(ctor_position, 1); + + ecs_fini(world); +} + +static +void AddPosition(ecs_iter_t *it) { } + +void ComponentLifecycle_set_lifecycle_after_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, AddPosition, EcsOnAdd, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position) + }); + + ecs_new(world, Position); + + test_int(ctor_position, 1); + + ecs_fini(world); +} + +static int dummy_dtor_invoked = 0; + +typedef struct Dummy { + int value; +} Dummy; + +static +void dummy_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + int i; + for (i = 0; i < count; i ++) { + test_assert(ecs_is_valid(world, entity_ptr[i])); + test_assert(ecs_is_alive(world, entity_ptr[i])); + } + + dummy_dtor_invoked ++; +} + +void ComponentLifecycle_valid_entity_in_dtor_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Dummy); + + ecs_set(world, ecs_id(Dummy), EcsComponentLifecycle, { + .dtor = dummy_dtor + }); + + ecs_entity_t e = ecs_new(world, Dummy); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(!ecs_is_valid(world, e)); + test_assert(!ecs_is_alive(world, e)); + + test_int(dummy_dtor_invoked, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_ctor_w_emplace() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + cl_ctx ctx = { { 0 } }; + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = comp_ctor, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + const Position *ptr = ecs_emplace(world, e, Position); + test_assert(ptr != NULL); + test_int(ctx.ctor.invoked, 0); + + ecs_fini(world); +} + +void ComponentLifecycle_dtor_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Dummy); + + ecs_set(world, ecs_id(Dummy), EcsComponentLifecycle, { + .dtor = dummy_dtor + }); + + ecs_entity_t e = ecs_new(world, Dummy); + test_assert(e != 0); + + test_int(dummy_dtor_invoked, 0); + + ecs_fini(world); + + test_int(dummy_dtor_invoked, 1); +} + +static +void type_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + test_int(count, 1); + test_assert(entity_ptr != NULL); + + ecs_entity_t e = entity_ptr[0]; + test_assert(e != 0); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + + test_int(ecs_vector_count(type), 3); + + dummy_dtor_invoked = 1; +} + +typedef struct { + ecs_entity_t e; +} Entity; + +typedef struct { + char *str; +} String; + +static int other_dtor_invoked; +static int other_dtor_valid_entity; + +static +void other_type_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + test_int(count, 1); + test_assert(entity_ptr != NULL); + test_assert(ptr != NULL); + + ecs_entity_t e = entity_ptr[0]; + test_assert(e != 0); + test_assert(ecs_is_valid(world, e)); + + Entity *comp = ptr; + test_assert(comp->e != 0); + + if (ecs_is_valid(world, comp->e)) { + ecs_type_t type = ecs_get_type(world, comp->e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 2); + other_dtor_valid_entity ++; + } + + other_dtor_invoked ++; +} + +ECS_COMPONENT_DECLARE(String); +ECS_COMPONENT_DECLARE(Entity); + +static +void other_comp_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + test_int(count, 1); + test_assert(entity_ptr != NULL); + test_assert(ptr != NULL); + + Entity *comp = ptr; + + ecs_entity_t e = entity_ptr[0]; + test_assert(e != 0); + test_assert(ecs_is_valid(world, e)); + + test_assert(comp->e != 0); + + if (ecs_is_valid(world, comp->e)) { + if (ecs_has(world, comp->e, String)) { + const String *str_ptr = ecs_get(world, comp->e, String); + test_assert(str_ptr != NULL); + test_assert(str_ptr->str != NULL); + test_str(str_ptr->str, "Foo"); + other_dtor_valid_entity ++; + } else { + test_assert(ecs_get(world, comp->e, String) == NULL); + } + } + + other_dtor_invoked ++; +} + +static +void other_delete_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + test_int(count, 1); + test_assert(entity_ptr != NULL); + test_assert(ptr != NULL); + + Entity *comp = ptr; + + ecs_entity_t e = entity_ptr[0]; + test_assert(e != 0); + test_assert(ecs_is_valid(world, e)); + + test_assert(comp->e != 0); + + if (ecs_is_valid(world, comp->e)) { + ecs_delete(world, comp->e); + other_dtor_valid_entity ++; + + test_assert(ecs_is_valid(world, e)); + test_assert(entity_ptr[0] == e); + test_assert(ecs_get(world, e, Entity) == comp); + test_assert(ecs_is_valid(world, comp->e)); + } + + other_dtor_invoked ++; +} + +static +void self_delete_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + test_int(count, 1); + test_assert(entity_ptr != NULL); + test_assert(ptr != NULL); + + ecs_entity_t e = entity_ptr[0]; + test_assert(e != 0); + + if (ecs_is_valid(world, e)) { + ecs_delete(world, e); + + // Delete should be deferred + test_assert(ecs_is_valid(world, e)); + } + + dummy_dtor_invoked ++; +} + +static +void string_dtor( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *entity_ptr, + void *ptr, + size_t size, + int32_t count, + void *ctx) +{ + test_int(count, 1); + test_assert(entity_ptr != NULL); + test_assert(ptr != NULL); + + String *comp = ptr; + ecs_entity_t e = entity_ptr[0]; + test_assert(e != 0); + test_assert(ecs_is_valid(world, e)); + + test_assert(comp->str != NULL); + ecs_os_free(comp->str); +} + +void ComponentLifecycle_valid_type_in_dtor_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Dummy); + + ecs_set(world, ecs_id(Dummy), EcsComponentLifecycle, { + .dtor = type_dtor + }); + + ecs_entity_t e = ecs_new(world, Dummy); + test_assert(e != 0); + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + test_int(dummy_dtor_invoked, 0); + + ecs_fini(world); + + test_int(dummy_dtor_invoked, 1); +} + +void ComponentLifecycle_valid_other_type_of_entity_in_dtor_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT_DEFINE(world, Entity); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_type_dtor + }); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_set(world, e2, Entity, {e1}); + + ecs_add(world, e1, Velocity); + ecs_set(world, e1, Entity, {e2}); + + test_int(other_dtor_invoked, 0); + + ecs_fini(world); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); +} + +void ComponentLifecycle_valid_same_type_comp_of_entity_in_dtor_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_comp_dtor + }); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_fini(world); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); +} + +void ComponentLifecycle_valid_same_type_comp_of_entity_in_dtor_on_delete_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_comp_dtor + }); + + ecs_entity_t parent = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_delete(world, parent); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_valid_entity_bulk_remove_all_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + ECS_TYPE(world, Type, String, Entity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_comp_dtor + }); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_bulk_remove(world, Type, NULL); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_in_dtor_same_type_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_delete_dtor + }); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_fini(world); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); +} + +void ComponentLifecycle_delete_in_dtor_other_type_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_delete_dtor + }); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Velocity); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_fini(world); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); +} + +void ComponentLifecycle_delete_self_in_dtor_on_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Dummy); + + ecs_set(world, ecs_id(Dummy), EcsComponentLifecycle, { + .dtor = self_delete_dtor + }); + + ecs_new(world, Dummy); + ecs_new(world, Dummy); + ecs_new(world, Dummy); + + test_int(dummy_dtor_invoked, 0); + + ecs_fini(world); + + test_int(dummy_dtor_invoked, 3); +} + +void ComponentLifecycle_delete_in_dtor_same_type_on_delete_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_delete_dtor + }); + + ecs_entity_t parent = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_delete(world, parent); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_in_dtor_other_type_on_delete_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_delete_dtor + }); + + ecs_entity_t parent = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Velocity); + + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_delete(world, parent); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_self_in_dtor_on_delete_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Dummy); + + ecs_set(world, ecs_id(Dummy), EcsComponentLifecycle, { + .dtor = self_delete_dtor + }); + + ecs_entity_t parent = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new(world, Dummy); + ecs_entity_t e2 = ecs_new(world, Dummy); + ecs_entity_t e3 = ecs_new(world, Dummy); + + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + ecs_add_pair(world, e3, EcsChildOf, parent); + + test_int(dummy_dtor_invoked, 0); + + ecs_delete(world, parent); + + test_int(dummy_dtor_invoked, 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_in_dtor_same_type_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_delete_dtor + }); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_set(world, e1, Entity, {e2}); + ecs_set(world, e2, Entity, {e3}); + ecs_set(world, e3, Entity, {e1}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e3, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_delete(world, e3); + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_delete(world, e2); + ecs_delete(world, e1); + + test_int(other_dtor_invoked, 3); + test_int(other_dtor_valid_entity, 2); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_in_dtor_other_type_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, String); + ECS_COMPONENT_DEFINE(world, Entity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_set(world, ecs_id(String), EcsComponentLifecycle, { + .dtor = string_dtor + }); + + ecs_set(world, ecs_id(Entity), EcsComponentLifecycle, { + .dtor = other_delete_dtor + }); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Velocity); + + ecs_set(world, e2, Entity, {e1}); + ecs_set(world, e1, Entity, {e2}); + + ecs_set(world, e1, String, {ecs_os_strdup("Foo")}); + ecs_set(world, e2, String, {ecs_os_strdup("Foo")}); + + test_int(other_dtor_invoked, 0); + + ecs_delete(world, e1); + ecs_delete(world, e2); + + test_int(other_dtor_invoked, 2); + test_int(other_dtor_valid_entity, 1); + + ecs_fini(world); +} + +void ComponentLifecycle_delete_self_in_dtor_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Dummy); + + ecs_set(world, ecs_id(Dummy), EcsComponentLifecycle, { + .dtor = self_delete_dtor + }); + + ecs_entity_t e1 = ecs_new(world, Dummy); + ecs_entity_t e2 = ecs_new(world, Dummy); + ecs_entity_t e3 = ecs_new(world, Dummy); + + test_int(dummy_dtor_invoked, 0); + + ecs_delete(world, e1); + ecs_delete(world, e2); + ecs_delete(world, e3); + + test_int(dummy_dtor_invoked, 3); + + ecs_fini(world); +} + +static int position_on_set_invoked; +static int velocity_on_set_invoked; + +ECS_ON_SET(Position, ptr, { + ptr->x += 1; + ptr->y += 2; + position_on_set_invoked ++; +}) + +ECS_ON_SET(Velocity, ptr, { + ptr->x += 1; + ptr->y += 2; + velocity_on_set_invoked ++; +}) + +void ComponentLifecycle_on_set_after_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .ctor = ecs_ctor(Position), + .on_set = ecs_on_set(Position) + }); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctor_position, 1); + test_int(position_on_set_invoked, 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctor_position, 1); + test_int(position_on_set_invoked, 1); + + test_assert(p == ecs_get(world, e, Position)); + test_int(p->x, 11); + test_int(p->y, 22); + + ecs_fini(world); +} + +void ComponentLifecycle_on_set_after_new_w_data() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .on_set = ecs_on_set(Position) + }); + + ecs_set(world, ecs_id(Velocity), EcsComponentLifecycle, { + .on_set = ecs_on_set(Velocity) + }); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position), + ecs_id(Velocity) + }, + .count = 2 + }, + (void*[]){ + (Position[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]) { + {1, 2}, + {3, 4}, + {5, 6} + } + }); + + ecs_entity_t e1 = ids[0]; + ecs_entity_t e2 = ids[1]; + ecs_entity_t e3 = ids[2]; + + test_int(position_on_set_invoked, 3); + test_int(velocity_on_set_invoked, 3); + + const Position *pos = ecs_get(world, e1, Position); + test_int(pos->x, 11); + test_int(pos->y, 22); + + pos = ecs_get(world, e2, Position); + test_int(pos->x, 31); + test_int(pos->y, 42); + + pos = ecs_get(world, e3, Position); + test_int(pos->x, 51); + test_int(pos->y, 62); + + const Velocity *vel = ecs_get(world, e1, Velocity); + test_int(vel->x, 2); + test_int(vel->y, 4); + + vel = ecs_get(world, e2, Velocity); + test_int(vel->x, 4); + test_int(vel->y, 6); + + vel = ecs_get(world, e3, Velocity); + test_int(vel->x, 6); + test_int(vel->y, 8); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Count.c b/fggl/ecs2/flecs/test/api/src/Count.c new file mode 100644 index 0000000000000000000000000000000000000000..ece78a5b1938972ff2d5590c96de99fa64cd9f3d --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Count.c @@ -0,0 +1,89 @@ +#include <api.h> + +void Count_count_empty() { + ecs_world_t *world = ecs_init(); + + test_int(ecs_count(world, 0), 0); + + ecs_fini(world); +} + +void Count_count_w_entity_0() { + ecs_world_t *world = ecs_init(); + + test_int(ecs_count_entity(world, 0), 0); + + ecs_fini(world); +} + +void Count_count_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + test_int(ecs_count(world, Position), 2); + + ecs_fini(world); +} + +void Count_count_2_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_ENTITY(world, e1, AND | Type); + ECS_ENTITY(world, e2, AND | Type); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Velocity); + + test_int(ecs_count(world, Type), 2); + + ecs_fini(world); +} + +void Count_count_3_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + + ECS_ENTITY(world, e1, AND | Type); + ECS_ENTITY(world, e2, AND | Type); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Velocity); + ECS_ENTITY(world, e5, Position, Velocity); + ECS_ENTITY(world, e6, Position, Mass); + ECS_ENTITY(world, e7, Velocity, Mass); + + test_int(ecs_count(world, Type), 2); + + ecs_fini(world); +} + +void Count_count_2_types_2_comps() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_ENTITY(world, e1, AND | Type); + ECS_ENTITY(world, e2, AND | Type); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Velocity); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Mass); + ECS_ENTITY(world, e7, Velocity, Mass); + + test_int(ecs_count(world, Type), 3); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/DeferredActions.c b/fggl/ecs2/flecs/test/api/src/DeferredActions.c new file mode 100644 index 0000000000000000000000000000000000000000..11df6fb502bedb9cb4100a1902b94a0d2d423cae --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/DeferredActions.c @@ -0,0 +1,2081 @@ +#include <api.h> + +void DeferredActions_defer_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void DeferredActions_defer_bulk_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + const ecs_entity_t *temp_ids = ecs_bulk_new(world, Position, 3); + ecs_entity_t ids[3]; + memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 3); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + ecs_fini(world); +} + +void DeferredActions_defer_bulk_new_w_data() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + const ecs_entity_t *temp_ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position)}, + .count = 1 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + } + }); + + ecs_entity_t ids[3]; + memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 3); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} + +void DeferredActions_defer_bulk_new_w_data_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_entity_t pair_id = ecs_pair(Pair, ecs_id(Position)); + + const ecs_entity_t *temp_ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){pair_id}, + .count = 1 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + } + }); + + ecs_entity_t ids[3]; + memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 3); + + test_assert(!ecs_has_entity(world, ids[0], pair_id)); + test_assert(!ecs_has_entity(world, ids[1], pair_id)); + test_assert(!ecs_has_entity(world, ids[2], pair_id)); + + ecs_defer_end(world); + + test_assert(!ecs_has_entity(world, ids[0], pair_id)); + test_assert(!ecs_has_entity(world, ids[1], pair_id)); + test_assert(!ecs_has_entity(world, ids[2], pair_id)); + + ecs_defer_end(world); + + test_assert(ecs_has_entity(world, ids[0], pair_id)); + test_assert(ecs_has_entity(world, ids[1], pair_id)); + test_assert(ecs_has_entity(world, ids[2], pair_id)); + + ecs_frame_end(world); + + test_assert(ecs_has_entity(world, ids[0], pair_id)); + test_assert(ecs_has_entity(world, ids[1], pair_id)); + test_assert(ecs_has_entity(world, ids[2], pair_id)); + + const Position * + p = ecs_get_id(world, ids[0], pair_id); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get_id(world, ids[1], pair_id); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get_id(world, ids[2], pair_id); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} + +void DeferredActions_defer_bulk_new_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + const ecs_entity_t *temp_ids = ecs_bulk_new(world, Type, 3); + ecs_entity_t ids[3]; + memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 3); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + test_assert(!ecs_has(world, ids[0], Velocity)); + test_assert(!ecs_has(world, ids[1], Velocity)); + test_assert(!ecs_has(world, ids[2], Velocity)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + test_assert(!ecs_has(world, ids[0], Velocity)); + test_assert(!ecs_has(world, ids[1], Velocity)); + test_assert(!ecs_has(world, ids[2], Velocity)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Velocity)); + test_assert(ecs_has(world, ids[1], Velocity)); + test_assert(ecs_has(world, ids[2], Velocity)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Velocity)); + test_assert(ecs_has(world, ids[1], Velocity)); + test_assert(ecs_has(world, ids[2], Velocity)); + + ecs_fini(world); +} + +void DeferredActions_defer_bulk_new_w_data_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + const ecs_entity_t *temp_ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position), ecs_id(Velocity)}, + .count = 2 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]){ + {1, 2}, + {3, 4}, + {5, 6} + } + }); + + ecs_entity_t ids[3]; + memcpy(ids, temp_ids, sizeof(ecs_entity_t) * 3); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + test_assert(!ecs_has(world, ids[0], Velocity)); + test_assert(!ecs_has(world, ids[1], Velocity)); + test_assert(!ecs_has(world, ids[2], Velocity)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, ids[0], Position)); + test_assert(!ecs_has(world, ids[1], Position)); + test_assert(!ecs_has(world, ids[2], Position)); + + test_assert(!ecs_has(world, ids[0], Velocity)); + test_assert(!ecs_has(world, ids[1], Velocity)); + test_assert(!ecs_has(world, ids[2], Velocity)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Velocity)); + test_assert(ecs_has(world, ids[1], Velocity)); + test_assert(ecs_has(world, ids[2], Velocity)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Velocity)); + test_assert(ecs_has(world, ids[1], Velocity)); + test_assert(ecs_has(world, ids[2], Velocity)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + const Velocity * + v = ecs_get(world, ids[0], Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + + v = ecs_get(world, ids[1], Velocity); + test_int(v->x, 3); + test_int(v->y, 4); + + v = ecs_get(world, ids[2], Velocity); + test_int(v->x, 5); + test_int(v->y, 6); + + ecs_fini(world); +} + +void DeferredActions_defer_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_add(world, e, Position); + + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_add(world, e, Type); + + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void DeferredActions_defer_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_remove(world, e, Position); + + test_assert(ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_frame_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void DeferredActions_defer_remove_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_remove(world, e, Type); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_frame_end(world); + + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void DeferredActions_defer_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + ecs_set(world, e, Position, {1, 2}); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_set(world, e, Position, {3, 4}); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 4); + + ecs_frame_end(world); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 4); + + ecs_fini(world); +} + +void DeferredActions_defer_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_delete(world, e); + + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void DeferredActions_defer_twice() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_defer_begin(world); + ecs_defer_begin(world); + + ecs_set(world, e, Velocity, {1, 2}); + + ecs_defer_end(world); + test_assert(!ecs_has(world, e, Velocity)); + ecs_defer_end(world); + test_assert(ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_fini(world); +} + +void DeferredActions_defer_twice_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 0); + ecs_staging_begin(world); + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_defer_begin(stage); + ecs_defer_begin(stage); + + ecs_set(stage, e, Velocity, {1, 2}); + + ecs_defer_end(stage); + test_assert(!ecs_has(stage, e, Velocity)); + ecs_defer_end(stage); + test_assert(!ecs_has(stage, e, Velocity)); + + ecs_staging_end(world); + test_assert(ecs_has(world, e, Velocity)); + ecs_frame_end(world); + test_assert(ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_fini(world); +} + +static +void AddVelocity(ecs_iter_t *it) { + ecs_type_t ecs_type(Velocity) = ecs_column_type(it, 2); + + ecs_defer_begin(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } + + ecs_defer_end(it->world); +} + +void DeferredActions_run_w_defer() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddVelocity, 0, Position, [in] :Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_run(world, AddVelocity, 0, NULL); + + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void DeferredActions_system_in_progress_w_defer() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddVelocity, EcsOnUpdate, Position, [in] :Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 0); + + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +static bool on_set_invoked = 0; + +static +void OnSetVelocity(ecs_iter_t *it) { + on_set_invoked = 1; +} + +void DeferredActions_defer_get_mut_no_modify() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_defer_begin(world); + + Velocity *v = ecs_get_mut(world, e, Velocity, NULL); + v->x = 1; + v->y = 2; + + test_assert(!on_set_invoked); + + ecs_defer_end(world); + + test_assert(!on_set_invoked); + + test_assert(ecs_has(world, e, Velocity)); + const Velocity *vptr = ecs_get(world, e, Velocity); + test_int(vptr->x, 1); + test_int(vptr->y, 2); + + ecs_fini(world); +} + +void DeferredActions_defer_get_mut_w_modify() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_defer_begin(world); + + Velocity *v = ecs_get_mut(world, e, Velocity, NULL); + v->x = 1; + v->y = 2; + test_assert(!on_set_invoked); + + ecs_modified(world, e, Velocity); + + test_assert(!on_set_invoked); + + ecs_defer_end(world); + + test_assert(on_set_invoked); + + test_assert(ecs_has(world, e, Velocity)); + const Velocity *vptr = ecs_get(world, e, Velocity); + test_int(vptr->x, 1); + test_int(vptr->y, 2); + + ecs_fini(world); +} + +void DeferredActions_defer_modify() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, Velocity); + + ecs_entity_t e = ecs_new(world, Velocity); + + ecs_defer_begin(world); + + ecs_modified(world, e, Velocity); + + test_assert(!on_set_invoked); + + ecs_defer_end(world); + + test_assert(on_set_invoked); + + ecs_fini(world); +} + +void DeferredActions_defer_set_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_defer_begin(world); + + ecs_set_pair(world, e, Velocity, ecs_id(Position), {1, 2}); + + ecs_defer_end(world); + + test_assert(ecs_has_pair(world, e, ecs_id(Velocity), ecs_id(Position))); + + ecs_fini(world); +} + +void DeferredActions_defer_clear() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_clear(world, e); + + test_assert(ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_frame_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, e); + ecs_add(world, e, Velocity); + + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + test_int(ecs_count(world, Position), 0); + test_int(ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void DeferredActions_defer_set_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, e); + ecs_set(world, e, Velocity, {1, 2}); + + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + test_int(ecs_count(world, Position), 0); + test_int(ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void DeferredActions_defer_get_mut_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, e); + Velocity *v = ecs_get_mut(world, e, Velocity, NULL); + v->x = 1; + v->y = 2; + + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + test_int(ecs_count(world, Position), 0); + test_int(ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void DeferredActions_defer_get_mut_after_delete_2nd_to_last() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + /* Create 2nd position. This will cause deletion of the entity in the sparse + * set to take a different path since it's not the last. */ + ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, e); + Velocity *v = ecs_get_mut(world, e, Velocity, NULL); + v->x = 1; + v->y = 2; + + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + test_int(ecs_count(world, Position), 1); + test_int(ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void DeferredActions_defer_add_child_to_deleted_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_add(world, child, Velocity); + ecs_delete(world, parent); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, parent)); + test_assert(!ecs_is_alive(world, child)); + + ecs_fini(world); +} + +void DeferredActions_recreate_deleted_entity_while_deferred() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e_old = ecs_new(world, 0); + test_assert(e_old != 0); + + ecs_delete(world, e_old); + test_assert(!ecs_is_alive(world, e_old)); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(e != e_old); + test_assert((e & ECS_ENTITY_MASK) == (e_old & ECS_ENTITY_MASK)); + + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_frame_end(world); + + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_recycled_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_delete(world, id); + + ecs_entity_t id_2 = ecs_new_id(world); + test_assert(id != id_2); + test_assert((int32_t)id == (int32_t)id_2); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_id(world, id_2); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, id)); + test_assert(ecs_is_alive(world, id_2)); + test_assert(ecs_is_alive(world, child)); + test_assert(ecs_has_id(world, child, id_2)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_recycled_id_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_delete(world, id); + + ecs_entity_t id_2 = ecs_new_id(world); + test_assert(id != id_2); + test_assert((int32_t)id == (int32_t)id_2); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_id(world, ECS_DISABLED | id_2); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, id)); + test_assert(ecs_is_alive(world, id_2)); + test_assert(ecs_is_alive(world, child)); + test_assert(ecs_has_id(world, child, ECS_DISABLED | id_2)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_recycled_relation() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t object = ecs_new_id(world); + test_assert(object != 0); + + ecs_entity_t rel = ecs_new_id(world); + test_assert(rel != 0); + + ecs_delete(world, rel); + + ecs_entity_t rel_2 = ecs_new_id(world); + test_assert(rel != rel_2); + test_assert((int32_t)rel == (int32_t)rel_2); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, rel_2, object); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(ecs_is_alive(world, object)); + test_assert(!ecs_is_alive(world, rel)); + test_assert(ecs_is_alive(world, rel_2)); + test_assert(ecs_is_alive(world, child)); + test_assert(ecs_has_pair(world, child, rel_2, object)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_recycled_object() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, Rel); + + ecs_entity_t object = ecs_new(world, 0); + test_assert(object != 0); + + ecs_delete(world, object); + + ecs_entity_t object_2 = ecs_new_id(world); + test_assert(object != object_2); + test_assert((int32_t)object == (int32_t)object_2); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, Rel, object_2); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, object)); + test_assert(ecs_is_alive(world, object_2)); + test_assert(ecs_is_alive(world, child)); + test_assert(ecs_has_pair(world, child, Rel, object_2)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_recycled_object_childof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_delete(world, parent); + + ecs_entity_t parent_2 = ecs_new_id(world); + test_assert(parent != parent_2); + test_assert((int32_t)parent == (int32_t)parent_2); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent_2); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, parent)); + test_assert(ecs_is_alive(world, parent_2)); + test_assert(ecs_is_alive(world, child)); + test_assert(ecs_has_pair(world, child, EcsChildOf, parent_2)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_deleted_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, id); + ecs_entity_t child = ecs_new_w_id(world, id); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, id)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_id(world, child, id)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_deleted_id_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, id); + ecs_entity_t child = ecs_new_w_id(world, ECS_DISABLED | id); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, id)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_id(world, child, ECS_DISABLED | id)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_deleted_relation() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t object = ecs_new_id(world); + test_assert(object != 0); + + ecs_entity_t rel = ecs_new_id(world); + test_assert(rel != 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, rel); + ecs_entity_t child = ecs_new_w_pair(world, rel, object); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(ecs_is_alive(world, object)); + test_assert(!ecs_is_alive(world, rel)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_pair(world, child, rel, object)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_deleted_object() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TAG(world, Rel); + + ecs_entity_t object = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, object); + ecs_entity_t child = ecs_new_w_pair(world, Rel, object); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, object)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_pair(world, child, Rel, object)); + + ecs_fini(world); +} + +void DeferredActions_defer_add_to_deleted_object_childof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_delete(world, parent); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_add(world, child, Velocity); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, parent)); + test_assert(!ecs_is_alive(world, child)); + + ecs_fini(world); +} + +void DeferredActions_defer_delete_added_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_id(world, id); + ecs_add(world, child, Velocity); + ecs_delete(world, id); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, id)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_id(world, child, id)); + + ecs_fini(world); +} + +void DeferredActions_defer_delete_added_id_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_id(world, ECS_DISABLED | id); + ecs_add(world, child, Velocity); + ecs_delete(world, id); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, id)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_id(world, child, ECS_DISABLED | id)); + + ecs_fini(world); +} + +void DeferredActions_defer_delete_added_relation() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t object = ecs_new_id(world); + test_assert(object != 0); + + ecs_entity_t rel = ecs_new_id(world); + test_assert(rel != 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, rel, object); + ecs_add(world, child, Velocity); + ecs_delete(world, rel); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(ecs_is_alive(world, object)); + test_assert(!ecs_is_alive(world, rel)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_pair(world, child, rel, object)); + + ecs_fini(world); +} + +void DeferredActions_defer_delete_added_object() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TAG(world, Rel); + + ecs_entity_t object = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, Rel, object); + ecs_add(world, child, Velocity); + ecs_delete(world, object); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, object)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has_pair(world, child, Rel, object)); + + ecs_fini(world); +} + +void DeferredActions_defer_delete_added_object_childof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_add(world, child, Velocity); + ecs_delete(world, parent); + + ecs_defer_end(world); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, parent)); + test_assert(!ecs_is_alive(world, child)); + + ecs_fini(world); +} + +void DeferredActions_discard_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + + ecs_add(world, e, Position); + + test_assert(ecs_is_alive(world, e)); + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_is_alive(world, e)); + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void DeferredActions_discard_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + + ecs_remove(world, e, Position); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void DeferredActions_discard_add_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + + ecs_add(world, e, Type); + + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void DeferredActions_discard_remove_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + + ecs_remove(world, e, Type); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_defer_end(world); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void DeferredActions_discard_child() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(!ecs_has(world, e, Position)); + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, e); + test_assert(child != 0); + test_assert(!ecs_has_pair(world, child, EcsChildOf, e)); + + ecs_defer_end(world); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, child)); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has_pair(world, child, EcsChildOf, e)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, child)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, child)); + + ecs_fini(world); +} + +void DeferredActions_discard_child_w_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_frame_begin(world, 1); + + ecs_defer_begin(world); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(!ecs_has(world, e, Position)); + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + + ecs_entity_t child = ecs_new(world, 0); + test_assert(child != 0); + + ecs_add_pair(world, child, EcsChildOf, e); + test_assert(!ecs_has_pair(world, child, EcsChildOf, e)); + + ecs_defer_end(world); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, child)); + + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, child)); + + ecs_frame_end(world); + + test_assert(!ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, child)); + + ecs_fini(world); +} + +void DeferredActions_defer_return_value() { + ecs_world_t *world = ecs_init(); + + test_bool(ecs_defer_begin(world), true); + + test_bool(ecs_defer_begin(world), false); + + test_bool(ecs_defer_end(world), false); + + test_bool(ecs_defer_end(world), true); + + ecs_fini(world); +} + +void DeferredActions_defer_get_mut_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + ecs_entity_t e = ecs_new_id(world); + + ecs_defer_begin(world); + + Position *p = ecs_get_mut_pair(world, e, Position, Pair, NULL); + test_assert(p != NULL); + p->x = 10; + p->y = 20; + + ecs_defer_end(world); + + const Position *pc = ecs_get_pair(world, e, Position, Pair); + test_assert(pc != NULL); + test_int(pc->x, 10); + test_int(pc->y, 20); + + ecs_fini(world); +} + +void DeferredActions_async_stage_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_world_t *async = ecs_async_stage_new(world); + ecs_add(async, e, Position); + test_assert(!ecs_has(world, e, Position)); + ecs_merge(async); + test_assert(ecs_has(world, e, Position)); + + ecs_async_stage_free(async); + + ecs_fini(world); +} + +void DeferredActions_async_stage_add_twice() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new_id(world); + + ecs_world_t *async = ecs_async_stage_new(world); + ecs_add(async, e, Position); + test_assert(!ecs_has(world, e, Position)); + ecs_merge(async); + test_assert(ecs_has(world, e, Position)); + + ecs_add(async, e, Velocity); + test_assert(!ecs_has(world, e, Velocity)); + ecs_merge(async); + test_assert(ecs_has(world, e, Velocity)); + + ecs_async_stage_free(async); + + ecs_fini(world); +} + + +void DeferredActions_async_stage_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_world_t *async = ecs_async_stage_new(world); + ecs_remove(async, e, Position); + test_assert(ecs_has(world, e, Position)); + ecs_merge(async); + test_assert(!ecs_has(world, e, Position)); + + ecs_async_stage_free(async); + + ecs_fini(world); +} + +void DeferredActions_async_stage_clear() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_world_t *async = ecs_async_stage_new(world); + ecs_clear(async, e); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + ecs_merge(async); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_async_stage_free(async); + + ecs_fini(world); +} + +void DeferredActions_async_stage_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_world_t *async = ecs_async_stage_new(world); + ecs_delete(async, e); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + ecs_merge(async); + test_assert(!ecs_is_alive(world, e)); + + ecs_async_stage_free(async); + + ecs_fini(world); +} + +void DeferredActions_async_stage_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_world_t *async = ecs_async_stage_new(world); + ecs_entity_t e = ecs_new(async, 0); + ecs_add(async, e, Position); + ecs_add(async, e, Velocity); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + ecs_merge(async); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_is_alive(world, e)); + + ecs_async_stage_free(async); + + ecs_fini(world); +} + +void DeferredActions_async_stage_no_get() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_world_t *async = ecs_async_stage_new(world); + + test_expect_abort(); + ecs_get(async, e, Position); +} + +void DeferredActions_async_stage_readonly() { + ecs_world_t *world = ecs_init(); + + ecs_world_t *async = ecs_async_stage_new(world); + test_assert(!ecs_stage_is_readonly(async)); + + ecs_async_stage_free(async); + ecs_fini(world); +} + +void DeferredActions_async_stage_is_async() { + ecs_world_t *world = ecs_init(); + + ecs_world_t *async = ecs_async_stage_new(world); + test_assert(ecs_stage_is_async(async)); + + ecs_async_stage_free(async); + ecs_fini(world); +} + +static +void RegisterComponent(ecs_iter_t *it) { + ecs_entity_t ecs_id(Position) = ecs_component_init(it->world, + &(ecs_component_desc_t){ + .entity = {.name = "Position"}, + .size = sizeof(Position), + .alignment = ECS_ALIGNOF(Position) + }); + + test_assert(ecs_id(Position) != 0); +} + +void DeferredActions_register_component_while_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, RegisterComponent, EcsOnUpdate, 0); + + ecs_progress(world, 0); + + ecs_entity_t ecs_id(Position) = ecs_lookup(world, "Position"); + test_assert(ecs_id(Position) != 0); + test_assert(ecs_has(world, ecs_id(Position), EcsComponent)); + + ecs_entity_t e = ecs_new_id(world); + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has_id(world, e, ecs_id(Position))); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DeferredActions_register_component_while_staged() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_new_id(world); + ecs_entity_t canary = ecs_new_id(world); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_add_id(stage, canary, Tag); + + ecs_entity_t ecs_id(Position) = ecs_component_init(stage, + &(ecs_component_desc_t){ + .entity = {.name = "Position"}, + .size = sizeof(Position), + .alignment = ECS_ALIGNOF(Position) + }); + + test_assert(ecs_id(Position) != 0); + + ecs_set(stage, e, Position, {10, 20}); + test_assert(!ecs_has_id(world, e, ecs_id(Position))); + test_assert(!ecs_has_id(world, canary, Tag)); + + ecs_staging_end(world); + + test_assert(ecs_has_id(world, e, ecs_id(Position))); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DeferredActions_register_component_while_deferred() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_new_id(world); + ecs_entity_t canary = ecs_new_id(world); + + ecs_defer_begin(world); + + ecs_add_id(world, canary, Tag); + + ecs_entity_t ecs_id(Position) = ecs_component_init(world, + &(ecs_component_desc_t){ + .entity = {.name = "Position"}, + .size = sizeof(Position), + .alignment = ECS_ALIGNOF(Position) + }); + + test_assert(ecs_id(Position) != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(!ecs_has_id(world, e, ecs_id(Position))); + test_assert(!ecs_has_id(world, canary, Tag)); + + ecs_defer_end(world); + + ecs_fini(world); +} + +void DeferredActions_defer_enable() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + + ecs_defer_begin(world); + ecs_enable(world, e, false); + test_assert(!ecs_has_id(world, e, EcsDisabled)); + ecs_defer_end(world); + + test_assert(ecs_has_id(world, e, EcsDisabled)); + + ecs_fini(world); +} + +void DeferredActions_defer_disable() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_w_id(world, EcsDisabled); + + ecs_defer_begin(world); + ecs_enable(world, e, true); + test_assert(ecs_has_id(world, e, EcsDisabled)); + ecs_defer_end(world); + + test_assert(!ecs_has_id(world, e, EcsDisabled)); + + ecs_fini(world); +} + diff --git a/fggl/ecs2/flecs/test/api/src/Delete.c b/fggl/ecs2/flecs/test/api/src/Delete.c new file mode 100644 index 0000000000000000000000000000000000000000..db0dcd86cd5e47904d0fea864a3425921fff75fa --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Delete.c @@ -0,0 +1,723 @@ +#include <api.h> + +void Delete_setup() { + ecs_tracing_enable(-3); +} + +void Delete_delete_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_delete(world, e); + + test_assert(ecs_exists(world, e)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void Delete_delete_1_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(ecs_exists(world, e)); + test_assert(!ecs_is_alive(world, e)); + + ecs_delete(world, e); + test_assert(ecs_exists(world, e)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void Delete_delete_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(ecs_exists(world, e)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void Delete_delete_nonexist() { + ecs_world_t *world = ecs_init(); + + ecs_delete(world, 100); + test_assert(!ecs_exists(world, 100)); + test_assert(!ecs_is_alive(world, 100)); + + ecs_fini(world); +} + +void Delete_delete_1st_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + + ecs_delete(world, e1); + test_assert(ecs_exists(world, e1)); + test_assert(!ecs_is_alive(world, e1)); + + test_assert(ecs_exists(world, e2)); + test_assert(ecs_is_alive(world, e2)); + + test_assert(ecs_exists(world, e3)); + test_assert(ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +void Delete_delete_2nd_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + + ecs_delete(world, e2); + + test_assert(ecs_exists(world, e1)); + test_assert(ecs_is_alive(world, e1)); + + test_assert(ecs_exists(world, e2)); + test_assert(!ecs_is_alive(world, e2)); + + test_assert(ecs_exists(world, e3)); + test_assert(ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +void Delete_delete_3rd_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + + ecs_delete(world, e3); + + test_assert(ecs_exists(world, e1)); + test_assert(ecs_is_alive(world, e1)); + + test_assert(ecs_exists(world, e2)); + test_assert(ecs_is_alive(world, e2)); + + test_assert(ecs_exists(world, e3)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +void Delete_delete_2_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + + ecs_delete(world, e2); + ecs_delete(world, e3); + + test_assert(ecs_exists(world, e1)); + test_assert(ecs_is_alive(world, e1)); + + test_assert(ecs_exists(world, e2)); + test_assert(!ecs_is_alive(world, e2)); + + test_assert(ecs_exists(world, e3)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +void Delete_delete_3_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + + ecs_delete(world, e1); + ecs_delete(world, e2); + ecs_delete(world, e3); + + test_assert(ecs_exists(world, e1)); + test_assert(!ecs_is_alive(world, e1)); + + test_assert(ecs_exists(world, e2)); + test_assert(!ecs_is_alive(world, e2)); + + test_assert(ecs_exists(world, e3)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +static +void CreateEntity(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + const ecs_entity_t *ids = ecs_bulk_new(it->world, Position, 10); + test_assert(ids != NULL); +} + +static +void DeleteEntity(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + ecs_delete(it->world, it->entities[i]); + } +} + +static int on_remove_system_invoked; + +static +void OnRemoveSystem(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + on_remove_system_invoked ++; + } +} + +void Delete_delete_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, CreateEntity, EcsOnUpdate, [out] :Position); + ECS_SYSTEM(world, DeleteEntity, EcsOnStore, Position); + ECS_TRIGGER(world, OnRemoveSystem, EcsOnRemove, Position); + + ecs_progress(world, 0); + + test_int( ecs_count(world, Position), 0); + test_int(on_remove_system_invoked, 10); + + ecs_fini(world); +} + +void Delete_clear_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_clear(world, e); + test_assert(!ecs_get_type(world, e)); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 > e); + + ecs_fini(world); +} + +void Delete_clear_2_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + test_assert(e != 0); + + ecs_clear(world, e); + test_assert(!ecs_get_type(world, e)); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 > e); + + ecs_fini(world); +} + +void Delete_alive_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_delete(world, e); + + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_fini(world); +} + +void Delete_alive_after_clear() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_clear(world, e); + + test_assert(!ecs_get_type(world, e)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_fini(world); +} + +void Delete_alive_after_staged_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_begin(world); + ecs_delete(world, e); + ecs_defer_end(world); + + test_assert(ecs_exists(world, e)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void Delete_alive_while_staged() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_begin(world); + test_assert(ecs_is_alive(world, e)); + ecs_defer_end(world); + + ecs_fini(world); +} + +void Delete_alive_while_staged_w_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_begin(world); + test_assert(ecs_is_alive(world, e)); + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_fini(world); +} + +void Delete_alive_while_staged_w_delete_recycled_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_delete(world, e); + e = ecs_new(world, 0); + test_assert(ecs_is_alive(world, e)); + + ecs_defer_begin(world); + test_assert(ecs_is_alive(world, e)); + ecs_delete(world, e); + test_assert(ecs_is_alive(world, e)); + ecs_defer_end(world); + + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_fini(world); +} + +void Delete_alive_after_recycle() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + test_assert(ecs_is_alive(world, e2)); + test_assert(e != e2); + test_assert((ECS_ENTITY_MASK & e) == (ECS_ENTITY_MASK & e2)); + + ecs_fini(world); +} + +void Delete_delete_recycled() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e != e2); + test_assert((e2 & ECS_ENTITY_MASK) == (e & ECS_ENTITY_MASK)); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, e2)); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, e2)); + + ecs_fini(world); +} + +void Delete_get_alive_for_alive() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + ecs_entity_t a = ecs_get_alive(world, e); + test_assert(a != 0); + test_assert(e == a); + + ecs_fini(world); +} + +void Delete_get_alive_for_recycled() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + e = ecs_new(world, 0); + test_assert(ecs_entity_t_lo(e) != e); // Ensure id is recycled + + ecs_entity_t a = ecs_get_alive(world, ecs_entity_t_lo(e)); + test_assert(a != 0); + test_assert(e == a); + + ecs_fini(world); +} + +void Delete_get_alive_for_not_alive() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + // Ensure id has no generation + test_assert(ecs_entity_t_lo(e) == e); + + ecs_entity_t a = ecs_get_alive(world, e); + test_assert(a == 0); + + ecs_fini(world); +} + +void Delete_get_alive_w_generation_for_recycled_alive() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + e = ecs_new(world, 0); + test_assert(ecs_entity_t_lo(e) != e); + + ecs_entity_t a = ecs_get_alive(world, e); + test_assert(a != 0); + test_assert(e == a); + + ecs_fini(world); +} + +void Delete_get_alive_w_generation_for_recycled_not_alive() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + test_assert(ecs_exists(world, e)); + + e = ecs_new(world, 0); + test_assert(ecs_entity_t_lo(e) != e); + + ecs_delete(world, e); + + test_expect_abort(); + + // Will assert, because function is called with generation that is not alive + ecs_get_alive(world, e); +} + +void Delete_get_alive_for_0() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + ecs_get_alive(world, 0); +} + +void Delete_get_alive_for_nonexistent() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_get_alive(world, 1000); + test_assert(e == 0); + + ecs_fini(world); +} + +static int move_invoked = 0; +static int dtor_invoked = 0; + +static int dtor_x; +static int dtor_y; + +static int move_dst_x; +static int move_dst_y; + +static int move_src_x; +static int move_src_y; + +static ECS_DTOR(Position, ptr, { + dtor_invoked ++; + dtor_x = ptr->x; + dtor_y = ptr->y; +}); + +static ECS_MOVE(Position, dst, src, { + move_dst_x = dst->x; + move_dst_y = dst->y; + move_src_x = src->x; + move_src_y = src->y; + *dst = *src; + move_invoked ++; +}); + +void Delete_move_w_dtor_move() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .dtor = ecs_dtor(Position), + .move = ecs_move(Position), + }); + + ecs_entity_t e_1 = ecs_new_id(world); + test_assert(e_1 != 0); + + ecs_entity_t e_2 = ecs_new_id(world); + test_assert(e_2 != 0); + + ecs_set(world, e_1, Position, {10, 20}); + ecs_set(world, e_2, Position, {30, 40}); // append after e_1 + + ecs_delete(world, e_1); // move e_2 to e_1 + + test_assert(!ecs_is_alive(world, e_1)); + test_assert(ecs_is_alive(world, e_2)); + + // counter intuitive, but this is because in this case the move is + // responsible for cleaning up e_1, because e_2 is moved into e_1 + test_int(dtor_invoked, 1); + test_int(dtor_x, 30); + test_int(dtor_y, 40); + + test_int(move_invoked, 1); + test_int(move_dst_x, 10); + test_int(move_dst_y, 20); + test_int(move_src_x, 30); + test_int(move_src_y, 40); + + const Position *p = ecs_get(world, e_2, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Delete_move_w_dtor_no_move() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .dtor = ecs_dtor(Position) + }); + + ecs_entity_t e_1 = ecs_new_id(world); + test_assert(e_1 != 0); + + ecs_entity_t e_2 = ecs_new_id(world); + test_assert(e_2 != 0); + + ecs_set(world, e_1, Position, {10, 20}); + ecs_set(world, e_2, Position, {30, 40}); // append after e_1 + + ecs_delete(world, e_1); // move e_2 to e_1 + + test_assert(!ecs_is_alive(world, e_1)); + test_assert(ecs_is_alive(world, e_2)); + + test_int(dtor_invoked, 1); + test_int(dtor_x, 10); + test_int(dtor_y, 20); + + const Position *p = ecs_get(world, e_2, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Delete_move_w_no_dtor_move() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .move = ecs_move(Position) + }); + + ecs_entity_t e_1 = ecs_new_id(world); + test_assert(e_1 != 0); + + ecs_entity_t e_2 = ecs_new_id(world); + test_assert(e_2 != 0); + + ecs_set(world, e_1, Position, {10, 20}); + ecs_set(world, e_2, Position, {30, 40}); // append after e_1 + + ecs_delete(world, e_1); // move e_2 to e_1 + + test_assert(!ecs_is_alive(world, e_1)); + test_assert(ecs_is_alive(world, e_2)); + + test_int(move_invoked, 1); + test_int(move_dst_x, 10); + test_int(move_dst_y, 20); + test_int(move_src_x, 30); + test_int(move_src_y, 40); + + const Position *p = ecs_get(world, e_2, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Delete_wrap_generation_count() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t start = ecs_new_id(world); + ecs_entity_t e = start; + + for (int i = 0; i < 65535; i ++) { + ecs_delete(world, e); + e = ecs_new_id(world); + test_assert(e != start); + test_assert((uint32_t)e == start); + } + + ecs_delete(world, e); + e = ecs_new_id(world); + test_assert(e == start); + + for (int i = 0; i < 65535; i ++) { + ecs_delete(world, e); + e = ecs_new_id(world); + test_assert(e != start); + test_assert((uint32_t)e == start); + } + + ecs_delete(world, e); + e = ecs_new_id(world); + test_assert(e == start); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Delete_w_filter.c b/fggl/ecs2/flecs/test/api/src/Delete_w_filter.c new file mode 100644 index 0000000000000000000000000000000000000000..1cb3928014aac66b40c041b4368228ddb4c8df3a --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Delete_w_filter.c @@ -0,0 +1,568 @@ +#include <api.h> + +void Delete_w_filter_delete_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_bulk_new(world, Position, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Mass, 3)[0]; + + test_int( ecs_count(world, Position), 3); + test_int( ecs_count(world, Mass), 3); + + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_int( ecs_count(world, Position), 0); + test_int( ecs_count(world, Mass), 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Position); + test_int( ecs_count(world, Position), 1); + + ecs_fini(world); +} + +void Delete_w_filter_delete_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e1 = ecs_bulk_new(world, Type, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Mass, 3)[0]; + + test_int( ecs_count(world, Type), 3); + test_int( ecs_count(world, Mass), 3); + + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Type) + }); + + test_int( ecs_count(world, Type), 0); + test_int( ecs_count(world, Mass), 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type); + test_int( ecs_count(world, Type), 1); + + ecs_fini(world); +} + +void Delete_w_filter_delete_1_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, Position, Velocity); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_1), 6); + test_int( ecs_count(world, Type_2), 3); + test_int( ecs_count(world, Mass), 3); + + /* Delete both entities of Type_1 and Type_2 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Type_1) + }); + + test_int( ecs_count(world, Type_1), 0); + test_int( ecs_count(world, Type_2), 0); + test_int( ecs_count(world, Mass), 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(ecs_is_alive(world, e3)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_1); + ecs_new(world, Type_2); + test_int( ecs_count(world, Type_1), 2); + test_int( ecs_count(world, Type_2), 1); + + ecs_fini(world); +} + +void Delete_w_filter_delete_2_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_1), 6); + test_int( ecs_count(world, Type_2), 3); + + /* Mass is also in Type_2 */ + test_int( ecs_count(world, Mass), 6); + + /* Delete both entities of Type_1 and Type_2 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Type_1) + }); + + test_int( ecs_count(world, Type_1), 0); + test_int( ecs_count(world, Type_2), 0); + test_int( ecs_count(world, Mass), 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(ecs_is_alive(world, e3)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_1); + ecs_new(world, Type_2); + test_int( ecs_count(world, Type_1), 2); + test_int( ecs_count(world, Type_2), 1); + + ecs_fini(world); +} + +void Delete_w_filter_delete_except_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + ECS_TYPE(world, Type_3, Position, Velocity, Rotation); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Type_3, 3)[0]; + ecs_entity_t e4 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 and Type_3 */ + test_int( ecs_count(world, Type_1), 9); + test_int( ecs_count(world, Type_2), 3); + test_int( ecs_count(world, Type_3), 3); + + /* Mass is also in Type_2 */ + test_int( ecs_count(world, Mass), 6); + + /* Delete both entities of Type_1 and Type_3 but not Type_3 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Type_1), + .exclude = ecs_type(Mass) + }); + + test_int( ecs_count(world, Type_1), 3); /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_2), 3); /* Type_2 has Mass so not deleted */ + test_int( ecs_count(world, Type_3), 0); + test_int( ecs_count(world, Mass), 6); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + test_assert(ecs_is_alive(world, e4)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_1); + ecs_new(world, Type_3); + test_int( ecs_count(world, Type_1), 5); + test_int( ecs_count(world, Type_2), 3); + test_int( ecs_count(world, Type_3), 1); + + ecs_fini(world); +} + +void Delete_w_filter_delete_except_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + ECS_TYPE(world, Type_3, Position, Velocity, Rotation); + ECS_TYPE(world, Except, Mass, Rotation); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Type_3, 3)[0]; + ecs_entity_t e4 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 and Type_3 */ + test_int( ecs_count(world, Type_1), 9); + test_int( ecs_count(world, Type_2), 3); + test_int( ecs_count(world, Type_3), 3); + + /* Mass is also in Type_2 */ + test_int( ecs_count(world, Mass), 6); + + /* Delete both entities of Type_1 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Type_1), + .exclude = ecs_type(Except) + }); + + test_int( ecs_count(world, Type_1), 6); /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_2), 3); /* Type_2 has Mass so not deleted */ + test_int( ecs_count(world, Type_3), 3); + test_int( ecs_count(world, Mass), 6); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(ecs_is_alive(world, e3)); + test_assert(ecs_is_alive(world, e4)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_1); + test_int( ecs_count(world, Type_1), 7); + test_int( ecs_count(world, Type_2), 3); + test_int( ecs_count(world, Type_3), 3); + + ecs_fini(world); +} + +void Delete_w_filter_delete_with_any_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + ECS_TYPE(world, Type_3, Position, Velocity, Rotation); + ECS_TYPE(world, Include, Mass, Rotation); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Type_3, 3)[0]; + ecs_entity_t e4 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 and Type_3 */ + test_int( ecs_count(world, Type_1), 9); + test_int( ecs_count(world, Type_2), 3); + test_int( ecs_count(world, Type_3), 3); + + /* Mass is also in Type_2 */ + test_int( ecs_count(world, Mass), 6); + + /* Delete both entities of Type_1 and Type_3 but not Type_3 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Include), + .include_kind = EcsMatchAny + }); + + test_int( ecs_count(world, Type_1), 3); /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_2), 0); /* Type_2 has Mass so not deleted */ + test_int( ecs_count(world, Type_3), 0); + test_int( ecs_count(world, Mass), 0); + + test_assert(ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + test_assert(!ecs_is_alive(world, e4)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_2); + ecs_new(world, Type_3); + ecs_new(world, Mass); + test_int( ecs_count(world, Type_1), 5); + test_int( ecs_count(world, Type_2), 1); + test_int( ecs_count(world, Type_3), 1); + test_int( ecs_count(world, Mass), 2); + + ecs_fini(world); +} + +void Delete_w_filter_delete_except_all_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + ECS_TYPE(world, Type_3, Position, Velocity, Mass, Rotation); + ECS_TYPE(world, Exclude, Mass, Rotation); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Type_3, 3)[0]; + ecs_entity_t e4 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 and Type_3 */ + test_int( ecs_count(world, Type_1), 9); + test_int( ecs_count(world, Type_2), 6); + test_int( ecs_count(world, Type_3), 3); + + /* Mass is also in Type_2 and Type_3 */ + test_int( ecs_count(world, Mass), 9); + + /* Delete everything but Type_3 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .exclude = ecs_type(Exclude), + .exclude_kind = EcsMatchAll + }); + + test_int( ecs_count(world, Type_1), 3); /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_2), 3); /* Type_2 is superset of Type_3 */ + test_int( ecs_count(world, Type_3), 3); + test_int( ecs_count(world, Mass), 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(ecs_is_alive(world, e3)); + test_assert(!ecs_is_alive(world, e4)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_1); + ecs_new(world, Type_2); + ecs_new(world, Mass); + + test_int( ecs_count(world, Type_1), 5); + test_int( ecs_count(world, Type_2), 4); + test_int( ecs_count(world, Type_3), 3); + test_int( ecs_count(world, Mass), 5); + + ecs_fini(world); +} + +void Delete_w_filter_include_exact() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + ECS_TYPE(world, Type_3, Position, Velocity, Mass, Rotation); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Type_3, 3)[0]; + ecs_entity_t e4 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 and Type_3 */ + test_int( ecs_count(world, Type_1), 9); + test_int( ecs_count(world, Type_2), 6); + test_int( ecs_count(world, Type_3), 3); + + /* Mass is also in Type_2 and Type_3 */ + test_int( ecs_count(world, Mass), 9); + + /* Delete only Type_2 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Type_2), + .include_kind = EcsMatchExact + }); + + test_int( ecs_count(world, Type_1), 6); /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_2), 3); /* Type_2 is superset of Type_3 */ + test_int( ecs_count(world, Type_3), 3); + test_int( ecs_count(world, Mass), 6); + + test_assert(ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(ecs_is_alive(world, e3)); + test_assert(ecs_is_alive(world, e4)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_2); + + test_int( ecs_count(world, Type_1), 7); + test_int( ecs_count(world, Type_2), 4); + test_int( ecs_count(world, Type_3), 3); + test_int( ecs_count(world, Mass), 7); + + ecs_fini(world); +} + +void Delete_w_filter_exclude_exact() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + ECS_TYPE(world, Type_3, Position, Velocity, Mass, Rotation); + + ecs_entity_t e1 = ecs_bulk_new(world, Type_1, 3)[0]; + ecs_entity_t e2 = ecs_bulk_new(world, Type_2, 3)[0]; + ecs_entity_t e3 = ecs_bulk_new(world, Type_3, 3)[0]; + ecs_entity_t e4 = ecs_bulk_new(world, Mass, 3)[0]; + + /* Type_1 is superset of Type_2 and Type_3 */ + test_int( ecs_count(world, Type_1), 9); + test_int( ecs_count(world, Type_2), 6); + test_int( ecs_count(world, Type_3), 3); + + /* Mass is also in Type_2 and Type_3 */ + test_int( ecs_count(world, Mass), 9); + + /* Delete everything but Type_2 */ + ecs_bulk_delete(world, &(ecs_filter_t){ + .exclude = ecs_type(Type_2), + .exclude_kind = EcsMatchExact + }); + + test_int( ecs_count(world, Type_1), 3); /* Type_1 is superset of Type_2 */ + test_int( ecs_count(world, Type_2), 3); /* Type_2 is superset of Type_3 */ + test_int( ecs_count(world, Type_3), 0); + test_int( ecs_count(world, Mass), 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + test_assert(!ecs_is_alive(world, e4)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Type_1); + ecs_new(world, Type_3); + + test_int( ecs_count(world, Type_1), 5); + test_int( ecs_count(world, Type_2), 4); + test_int( ecs_count(world, Type_3), 1); + test_int( ecs_count(world, Mass), 4); + + ecs_fini(world); +} + +static bool invoked = false; + +static +void Dummy(ecs_iter_t *it) { + invoked = true; +} + +void Delete_w_filter_system_activate_test() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + ecs_entity_t e1 = ecs_bulk_new(world, Position, 3)[0]; + + test_int( ecs_count(world, Position), 3); + + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_int( ecs_count(world, Position), 0); + test_assert(!ecs_is_alive(world, e1)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Position); + test_int( ecs_count(world, Position), 1); + + /* Test if system is properly reactivated */ + ecs_progress(world, 0); + test_bool(invoked, true); + + ecs_fini(world); +} + +void Delete_w_filter_skip_builtin_tables() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + ecs_entity_t e1 = ecs_bulk_new(world, Position, 3)[0]; + + test_int( ecs_count(world, Position), 3); + + ecs_bulk_delete(world, NULL); + test_assert(ecs_is_valid(world, ecs_id(Position))); + test_assert(ecs_is_valid(world, Dummy)); + + test_int( ecs_count(world, Position), 0); + test_assert(!ecs_is_alive(world, e1)); + + test_assert(ecs_is_alive(world, ecs_id(Position))); + test_assert(ecs_is_alive(world, Dummy)); + test_assert(!!ecs_get_type(world, ecs_id(Position))); + test_assert(!!ecs_get_type(world, Dummy)); + + /* Test if table is left in a state that can be repopulated */ + ecs_new(world, Position); + test_int( ecs_count(world, Position), 1); + + /* Test if system is properly reactivated */ + ecs_progress(world, 0); + test_bool(invoked, true); + + ecs_fini(world); +} + +static int on_remove_count = 0; + +static +void RemovePosition(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + on_remove_count ++; + } +} + +void Delete_w_filter_delete_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, RemovePosition, EcsOnRemove, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + test_int( ecs_count(world, Position), 3); + + ecs_bulk_delete(world, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + + test_int(on_remove_count, 3); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/DirectAccess.c b/fggl/ecs2/flecs/test/api/src/DirectAccess.c new file mode 100644 index 0000000000000000000000000000000000000000..f9fe0c58286585567bc948799d5e69c5906ae1dc --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/DirectAccess.c @@ -0,0 +1,688 @@ + +#include <api.h> +#include <flecs/type.h> + +void DirectAccess_get_table_from_str() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *table = ecs_table_from_str(world, "Position, Velocity"); + test_assert(table != NULL); + + ecs_type_t type = ecs_table_get_type(table); + test_assert(type != NULL); + + test_int(ecs_vector_count(type), 2); + test_assert(ecs_type_owns_entity(world, type, ecs_id(Position), true)); + test_assert(ecs_type_owns_entity(world, type, ecs_id(Velocity), true)); + + ecs_fini(world); +} + +void DirectAccess_get_table_from_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_type_t type = ecs_type_from_str(world, "Position, Velocity"); + test_assert(type != NULL); + + ecs_table_t *table = ecs_table_from_type(world, type); + test_assert(table != NULL); + test_assert(type == ecs_table_get_type(table)); + + ecs_fini(world); +} + +void DirectAccess_insert_record() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *table = ecs_table_from_str(world, "Position, Velocity"); + test_assert(table != NULL); + + ecs_record_t r = ecs_table_insert(world, table, 0, NULL); + test_assert(r.table == table); + test_int(r.row, 1); + + test_int(ecs_table_count(table), 1); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_assert(it.table == table); + test_int(it.count, 1); + test_int(it.entities[0], 0); + + ecs_fini(world); +} + +void DirectAccess_insert_record_w_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *table = ecs_table_from_str(world, "Position, Velocity"); + test_assert(table != NULL); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + ecs_record_t *r_ptr = ecs_record_find(world, e); + test_assert(r_ptr != NULL); + test_assert(r_ptr->table == NULL); + test_int(r_ptr->row, 0); + + ecs_record_t r = ecs_table_insert(world, table, e, r_ptr); + test_assert(r.table == table); + test_int(r.row, 1); + test_assert(r_ptr->table == table); + test_int(r_ptr->row, 1); + + test_int(ecs_table_count(table), 1); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_assert(it.table == table); + test_int(it.count, 1); + test_int(it.entities[0], e); + + ecs_fini(world); +} + +void DirectAccess_table_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_new(world, Type); + ecs_new(world, Type); + ecs_new(world, Type); + + ecs_table_t *t = ecs_table_from_type(world, ecs_type(Type)); + test_assert(t != NULL); + test_assert(ecs_table_get_type(t) == ecs_type(Type)); + test_int(ecs_table_count(t), 3); + + ecs_fini(world); +} + +void DirectAccess_find_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_fini(world); +} + +void DirectAccess_get_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_set(world, ecs_set(world, 0, + Position, {10, 20}), + Velocity, {1, 2}); + + ecs_set(world, ecs_set(world, 0, + Position, {30, 40}), + Velocity, {3, 4}); + + ecs_set(world, ecs_set(world, 0, + Position, {50, 60}), + Velocity, {5, 6}); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_vector_t *v_p = ecs_table_get_column(t, 0); + test_assert(v_p != NULL); + + Position *p = ecs_vector_first(v_p, Position); + test_assert(p != NULL); + test_int(p[0].x, 10); + test_int(p[0].y, 20); + + test_int(p[1].x, 30); + test_int(p[1].y, 40); + + test_int(p[2].x, 50); + test_int(p[2].y, 60); + + ecs_vector_t *v_v = ecs_table_get_column(t, 1); + test_assert(v_v != NULL); + + Velocity *v = ecs_vector_first(v_v, Velocity); + test_assert(v != NULL); + test_int(v[0].x, 1); + test_int(v[0].y, 2); + + test_int(v[1].x, 3); + test_int(v[1].y, 4); + + test_int(v[2].x, 5); + test_int(v[2].y, 6); + + ecs_fini(world); +} + +void DirectAccess_get_empty_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + test_assert(ecs_table_get_column(t, 0) == NULL); + test_assert(ecs_table_get_column(t, 1) == NULL); + + ecs_fini(world); +} + +void DirectAccess_set_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + + ecs_vector_t *v_e = ecs_vector_new(ecs_entity_t, 3); + ecs_vector_add(&v_e, ecs_entity_t)[0] = 0; + ecs_vector_add(&v_e, ecs_entity_t)[0] = 0; + ecs_vector_add(&v_e, ecs_entity_t)[0] = 0; + + ecs_vector_t *v_r = ecs_vector_new(ecs_record_t*, 3); + ecs_vector_set_count(&v_r, ecs_record_t*, 3); + ecs_vector_zero(v_r, ecs_record_t*); + + ecs_table_set_entities(t, v_e, v_r); + + ecs_vector_t *v_p = ecs_vector_new(Position, 3); + ecs_vector_add(&v_p, Position)[0] = (Position){10, 20}; + ecs_vector_add(&v_p, Position)[0] = (Position){30, 40}; + ecs_vector_add(&v_p, Position)[0] = (Position){50, 60}; + ecs_table_set_column(world, t, 0, v_p); + + test_int(ecs_table_count(t), 3); + test_assert(ecs_table_get_entities(t) == v_e); + test_assert(ecs_table_get_records(t) == v_r); + test_assert(ecs_table_get_column(t, 0) == v_p); + + ecs_fini(world); +} + +void DirectAccess_delete_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_set(world, ecs_set(world, 0, + Position, {10, 20}), + Velocity, {1, 2}); + + ecs_set(world, ecs_set(world, 0, + Position, {30, 40}), + Velocity, {3, 4}); + + ecs_set(world, ecs_set(world, 0, + Position, {50, 60}), + Velocity, {5, 6}); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_vector_t *v_p = ecs_table_get_column(t, 0); + test_assert(v_p != NULL); + + ecs_table_delete_column(world, t, 0, NULL); + + test_assert(ecs_table_get_column(t, 0) == NULL); + test_assert(ecs_table_get_column(t, 1) != NULL); + + ecs_fini(world); +} + +void DirectAccess_delete_column_explicit() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_set(world, ecs_set(world, 0, + Position, {10, 20}), + Velocity, {1, 2}); + + ecs_set(world, ecs_set(world, 0, + Position, {30, 40}), + Velocity, {3, 4}); + + ecs_set(world, ecs_set(world, 0, + Position, {50, 60}), + Velocity, {5, 6}); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_vector_t *v_p = ecs_table_get_column(t, 0); + test_assert(v_p != NULL); + + ecs_table_delete_column(world, t, 0, v_p); + + ecs_fini(world); +} + +static int ctor_position = 0; +static int dtor_position = 0; +static int copy_position = 0; +static int move_position = 0; + +static ECS_CTOR(Position, ptr, { + ctor_position ++; + *ptr = (Position){0, 0}; +}); + +static ECS_DTOR(Position, ptr, { + dtor_position ++; + *ptr = (Position){0, 0}; +}); + +static ECS_COPY(Position, dst, src, { + copy_position ++; + *dst = *src; +}); + +static ECS_MOVE(Position, dst, src, { + move_position ++; + *dst = *src; +}); + +void DirectAccess_delete_column_w_dtor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_set(world, ecs_set(world, 0, + Position, {10, 20}), + Velocity, {1, 2}); + + ecs_set(world, ecs_set(world, 0, + Position, {30, 40}), + Velocity, {3, 4}); + + ecs_set(world, ecs_set(world, 0, + Position, {50, 60}), + Velocity, {5, 6}); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_vector_t *v_p = ecs_table_get_column(t, 0); + test_assert(v_p != NULL); + + ecs_set_component_actions(world, Position, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + ecs_table_delete_column(world, t, 0, NULL); + test_int(ctor_position, 0); + test_int(dtor_position, 3); + test_int(copy_position, 0); + test_int(move_position, 0); + + /* Column was deleted, also reset entities & records */ + ecs_vector_free(ecs_table_get_entities(t)); + ecs_vector_free(ecs_table_get_records(t)); + ecs_table_set_entities(t, NULL, NULL); + + test_assert(ecs_table_get_column(t, 0) == NULL); + test_assert(ecs_table_get_column(t, 1) != NULL); + + /* Delete velocity column to ensure consistency */ + ecs_table_delete_column(world, t, 1, NULL); + + ecs_fini(world); +} + +void DirectAccess_copy_to() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_record_t r = ecs_table_insert(world, t, 0, NULL); + test_assert(r.table == t); + test_int(r.row, 1); + + test_int(ecs_table_count(t), 1); + + ecs_set_component_actions(world, Position, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + ecs_record_copy_to(world, &r, 0, sizeof(Position), &(Position){10, 20}, 1); + test_int(ctor_position, 0); + test_int(dtor_position, 0); + test_int(copy_position, 1); + test_int(move_position, 0); + + Position *p = ecs_record_get_column(&r, 0, sizeof(Position)); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DirectAccess_copy_pod_to() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_record_t r = ecs_table_insert(world, t, 0, NULL); + test_assert(r.table == t); + test_int(r.row, 1); + + test_int(ecs_table_count(t), 1); + + ecs_set_component_actions(world, Position, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + /* This should not be done in a real application (copying a component with + * lifecycle actions with copy_pod_to) but for the testcase it is useful to + * verify that the copy/move actions are not being invoked when using this + * function. */ + ecs_record_copy_pod_to(world, &r, 0, sizeof(Position), &(Position){10, 20}, 1); + test_int(ctor_position, 0); + test_int(dtor_position, 0); + test_int(copy_position, 0); + test_int(move_position, 0); + + Position *p = ecs_record_get_column(&r, 0, sizeof(Position)); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DirectAccess_move_to() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_record_t r = ecs_table_insert(world, t, 0, NULL); + test_assert(r.table == t); + test_int(r.row, 1); + + test_int(ecs_table_count(t), 1); + + ecs_set_component_actions(world, Position, { + .ctor = ecs_ctor(Position), + .dtor = ecs_dtor(Position), + .copy = ecs_copy(Position), + .move = ecs_move(Position) + }); + + ecs_record_move_to(world, &r, 0, sizeof(Position), &(Position){10, 20}, 1); + test_int(ctor_position, 0); + test_int(dtor_position, 0); + test_int(copy_position, 0); + test_int(move_position, 1); + + Position *p = ecs_record_get_column(&r, 0, sizeof(Position)); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DirectAccess_copy_to_no_copy() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_record_t r = ecs_table_insert(world, t, 0, NULL); + test_assert(r.table == t); + test_int(r.row, 1); + + test_int(ecs_table_count(t), 1); + + ecs_record_copy_to(world, &r, 0, sizeof(Position), &(Position){10, 20}, 1); + + Position *p = ecs_record_get_column(&r, 0, sizeof(Position)); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DirectAccess_move_to_no_move() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_table_t *t = ecs_table_from_str(world, "Position, Velocity"); + test_assert(t != NULL); + + test_int(ecs_table_find_column(t, ecs_id(Position)), 0); + test_int(ecs_table_find_column(t, ecs_id(Velocity)), 1); + + ecs_record_t r = ecs_table_insert(world, t, 0, NULL); + test_assert(r.table == t); + test_int(r.row, 1); + + test_int(ecs_table_count(t), 1); + + ecs_record_move_to(world, &r, 0, sizeof(Position), &(Position){10, 20}, 1); + + Position *p = ecs_record_get_column(&r, 0, sizeof(Position)); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void DirectAccess_find_record_not_exists() { + ecs_world_t *world = ecs_init(); + + ecs_record_t *r_ptr = ecs_record_find(world, 1000); + test_assert(r_ptr == NULL); + + ecs_fini(world); +} + +void DirectAccess_get_entities_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_table_t *t = ecs_table_from_str(world, "Position"); + test_assert(t != NULL); + + test_assert(ecs_table_get_entities(t) == NULL); + + ecs_fini(world); +} + +void DirectAccess_get_records_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_table_t *t = ecs_table_from_str(world, "Position"); + test_assert(t != NULL); + + test_assert(ecs_table_get_records(t) == NULL); + + ecs_fini(world); +} + +void DirectAccess_get_column_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_table_t *t = ecs_table_from_str(world, "Position"); + test_assert(t != NULL); + + test_assert(ecs_table_get_column(t, 0) == NULL); + + ecs_fini(world); +} + +void DirectAccess_delete_column_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_table_t *t = ecs_table_from_str(world, "Position"); + test_assert(t != NULL); + + ecs_table_delete_column(world, t, 0, NULL); + + ecs_fini(world); +} + +void DirectAccess_get_record_column_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_table_t *t = ecs_table_from_str(world, "Position"); + test_assert(t != NULL); + + ecs_record_t r = {.table = t, .row = 0}; + + test_assert(ecs_record_get_column(&r, 0, sizeof(Position)) == NULL); + + ecs_fini(world); +} + +void DirectAccess_has_module() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { + .add = {EcsModule} + }); + + ecs_entity_t m_e = ecs_entity_init(world, &(ecs_entity_desc_t) { + .add = {ecs_pair(EcsChildOf, m)} + }); + + ecs_record_t *r = ecs_record_find(world, e); + test_assert(r != NULL); + test_assert(r->table != NULL); + test_assert(ecs_table_has_module(r->table) == false); + + r = ecs_record_find(world, ecs_id(Position)); + test_assert(r != NULL); + test_assert(r->table != NULL); + test_assert(ecs_table_has_module(r->table) == false); + + r = ecs_record_find(world, m); + test_assert(r != NULL); + test_assert(r->table != NULL); + test_assert(ecs_table_has_module(r->table) == true); + + r = ecs_record_find(world, m_e); + test_assert(r != NULL); + test_assert(r->table != NULL); + test_assert(ecs_table_has_module(r->table) == true); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/EnabledComponents.c b/fggl/ecs2/flecs/test/api/src/EnabledComponents.c new file mode 100644 index 0000000000000000000000000000000000000000..8d9b2d5cc96a9cebb584e5632a49058c38cdb1b9 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/EnabledComponents.c @@ -0,0 +1,1088 @@ +#include <api.h> + +void EnabledComponents_is_component_enabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_fini(world); +} + +void EnabledComponents_is_empty_entity_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + + test_bool(ecs_is_component_enabled(world, e, Position), false); + + ecs_fini(world); +} + +void EnabledComponents_is_0_entity_disabled() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + test_bool(ecs_is_component_enabled(world, 0, Position), false); +} + +void EnabledComponents_is_0_component_disabled() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + + test_expect_abort(); + + test_bool(ecs_is_component_enabled_w_entity(world, e, 0), false); +} + +void EnabledComponents_is_nonexist_component_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Velocity); + + test_bool(ecs_is_component_enabled(world, e, Position), false); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_component_enabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_fini(world); +} + +void EnabledComponents_is_disabled_component_enabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_fini(world); +} + +void EnabledComponents_has_enabled_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_enable_component(world, e, Position, true); + + test_bool(ecs_has_entity(world, e, ECS_DISABLED | ecs_id(Position)), true); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_add(world, e, Position); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_has_entity(world, e, ECS_DISABLED | ecs_id(Position)), true); + test_bool(ecs_has(world, e, Position), true); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_remove(world, e, Position); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_has_entity(world, e, ECS_DISABLED | ecs_id(Position)), true); + test_bool(ecs_has(world, e, Position), false); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_after_disable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_enable_component(world, e, Position, false); + test_bool(ecs_is_component_enabled(world, e, Position), false); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_fini(world); +} + +void EnabledComponents_is_disabled_after_enable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + + ecs_enable_component(world, e, Position, false); + test_bool(ecs_is_component_enabled(world, e, Position), false); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_enable_component(world, e, Position, false); + test_bool(ecs_is_component_enabled(world, e, Position), false); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_randomized() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t entities[100000]; + bool enabled[100000]; + + int32_t i; + for (i = 0; i < 100000; i ++) { + enabled[i] = rand() % 2; + entities[i] = ecs_new_id(world); + ecs_enable_component(world, entities[i], Position, enabled[i]); + } + + for (i = 0; i < 100000; i ++) { + test_bool( + ecs_is_component_enabled(world, entities[i], Position), enabled[i]); + } + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_after_add_randomized() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t entities[100000]; + bool enabled[100000]; + + int32_t i; + for (i = 0; i < 100000; i ++) { + enabled[i] = rand() % 2; + entities[i] = ecs_new_id(world); + ecs_enable_component(world, entities[i], Position, enabled[i]); + } + + for (i = 0; i < 100000; i ++) { + ecs_add(world, entities[i], Position); + } + + for (i = 0; i < 100000; i ++) { + test_bool( + ecs_is_component_enabled(world, entities[i], Position), enabled[i]); + } + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_after_randomized_add_randomized() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t entities[100000]; + bool enabled[100000]; + + int32_t i; + for (i = 0; i < 100000; i ++) { + enabled[i] = rand() % 2; + entities[i] = ecs_new_id(world); + ecs_enable_component(world, entities[i], Position, enabled[i]); + } + + for (i = 0; i < 100000; i ++) { + if (!(rand() % 5)) { + ecs_add(world, entities[i], Position); + } + } + + for (i = 0; i < 100000; i ++) { + test_bool( + ecs_is_component_enabled(world, entities[i], Position), enabled[i]); + } + + ecs_fini(world); +} + +void EnabledComponents_query_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_enable_component(world, e1, Position, true); + ecs_enable_component(world, e2, Position, false); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t table_count = 0, count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != e2); + test_assert(it.entities[i] == e1 || it.entities[i] == e3); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + table_count ++; + } + + test_int(count, 2); + test_int(table_count, 2); + + ecs_fini(world); +} + +void EnabledComponents_query_disabled_skip_initial() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_enable_component(world, e1, Position, false); + ecs_enable_component(world, e2, Position, true); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] != e1); + test_assert(it.entities[i] == e2 || it.entities[i] == e3); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, 2); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i, total_count = 0; + for (i = 0; i < 64; i ++) { + ecs_entity_t e = ecs_new(world, Position); + if (!(e % 2)) { + ecs_enable_component(world, e, Position, false); + } else { + ecs_enable_component(world, e, Position, true); + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] % 2); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_8() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + if (!(e % 8)) { + ecs_enable_component(world, e, Position, false); + } else { + ecs_enable_component(world, e, Position, true); + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] % 8); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_64() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + if (!(e % 64)) { + ecs_enable_component(world, e, Position, false); + } else { + ecs_enable_component(world, e, Position, true); + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] % 64); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_256() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + if (!(e % 256)) { + ecs_enable_component(world, e, Position, false); + } else { + ecs_enable_component(world, e, Position, true); + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] % 256); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_1024() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i, total_count = 0; + for (i = 0; i < 100000; i ++) { + ecs_entity_t e = ecs_new(world, Position); + if (!(e % 1024)) { + ecs_enable_component(world, e, Position, false); + } else { + ecs_enable_component(world, e, Position, true); + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] % 1024); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_enable_mod_10() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + if (!(e % 10)) { + ecs_enable_component(world, e, Position, true); + total_count ++; + } else { + ecs_enable_component(world, e, Position, false); + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(!(it.entities[i] % 10)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_enable_component(world, e, Velocity, false); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_enable_component(world, e, Velocity, false); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + + ecs_enable_component(world, e, Mass, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + test_bool(ecs_is_component_enabled(world, e, Mass), true); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_2_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_enable_component(world, e, Velocity, false); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + + ecs_add(world, e, Position); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + + ecs_fini(world); +} + +void EnabledComponents_is_enabled_3_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_enable_component(world, e, Position, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_enable_component(world, e, Velocity, false); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + + ecs_enable_component(world, e, Mass, true); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + test_bool(ecs_is_component_enabled(world, e, Mass), true); + + ecs_add(world, e, Position); + test_bool(ecs_is_component_enabled(world, e, Position), true); + test_bool(ecs_is_component_enabled(world, e, Velocity), false); + test_bool(ecs_is_component_enabled(world, e, Mass), true); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_2_2_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + if (!(e % 2)) { + ecs_enable_component(world, e, Position, true); + } else { + ecs_enable_component(world, e, Position, false); + } + + if (!(e % 3)) { + ecs_enable_component(world, e, Velocity, true); + } else { + ecs_enable_component(world, e, Velocity, false); + } + + if (!(e % 2) && !(e % 3)) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(!(it.entities[i] % 2) && !(it.entities[i] % 3)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_8_2_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + if (!(e % 8)) { + ecs_enable_component(world, e, Position, true); + } else { + ecs_enable_component(world, e, Position, false); + } + + if (!(e % 4)) { + ecs_enable_component(world, e, Velocity, true); + } else { + ecs_enable_component(world, e, Velocity, false); + } + + if (!(e % 8) && !(e % 4)) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(!(it.entities[i] % 8) && !(it.entities[i] % 4)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_64_2_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + if (!(e % 64)) { + ecs_enable_component(world, e, Position, true); + } else { + ecs_enable_component(world, e, Position, false); + } + + if (!(e % 16)) { + ecs_enable_component(world, e, Velocity, true); + } else { + ecs_enable_component(world, e, Velocity, false); + } + + if (!(e % 64) && !(e % 16)) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(!(it.entities[i] % 64) && !(it.entities[i] % 16)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_256_2_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + if (!(e % 256)) { + ecs_enable_component(world, e, Position, true); + } else { + ecs_enable_component(world, e, Position, false); + } + + if (!(e % 64)) { + ecs_enable_component(world, e, Velocity, true); + } else { + ecs_enable_component(world, e, Velocity, false); + } + + if (!(e % 256) && !(e % 64)) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(!(it.entities[i] % 256) && !(it.entities[i] % 64)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_mod_1024_2_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + if (!(e % 1024)) { + ecs_enable_component(world, e, Position, true); + } else { + ecs_enable_component(world, e, Position, false); + } + + if (!(e % 128)) { + ecs_enable_component(world, e, Velocity, true); + } else { + ecs_enable_component(world, e, Velocity, false); + } + + if (!(e % 1024) && !(e % 128)) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(!(it.entities[i] % 1024) && !(it.entities[i] % 128)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_randomized_2_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + bool enable_1 = rand() % 2; + ecs_enable_component(world, e, Position, enable_1); + bool enable_2 = rand() % 2; + ecs_enable_component(world, e, Velocity, enable_2); + + if (enable_1 && enable_2) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_randomized_3_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + ecs_add(world, e, Mass); + + bool enable_1 = rand() % 2; + ecs_enable_component(world, e, Position, enable_1); + bool enable_2 = rand() % 2; + ecs_enable_component(world, e, Velocity, enable_2); + bool enable_3 = rand() % 2; + ecs_enable_component(world, e, Mass, enable_3); + + if (enable_1 && enable_2 && enable_3) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity, Mass"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Mass)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_query_randomized_4_bitsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + int32_t i, total_count = 0; + for (i = 0; i < 65536; i ++) { + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + ecs_add(world, e, Mass); + ecs_add(world, e, Rotation); + + bool enable_1 = rand() % 2; + ecs_enable_component(world, e, Position, enable_1); + bool enable_2 = rand() % 2; + ecs_enable_component(world, e, Velocity, enable_2); + bool enable_3 = rand() % 2; + ecs_enable_component(world, e, Mass, enable_3); + bool enable_4 = rand() % 2; + ecs_enable_component(world, e, Rotation, enable_4); + + if (enable_1 && enable_2 && enable_3 && enable_4) { + total_count ++; + } + } + + test_assert(total_count != 0); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity, Mass, Rotation"); + ecs_iter_t it = ecs_query_iter(q); + + int32_t count = 0; + + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_is_component_enabled(world, it.entities[i], Position)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Velocity)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Mass)); + test_assert(ecs_is_component_enabled(world, it.entities[i], Rotation)); + } + count += it.count; + } + + test_int(count, total_count); + + ecs_fini(world); +} + +void EnabledComponents_defer_enable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_bool(ecs_is_component_enabled(world, e, Position), true); + + ecs_defer_begin(world); + ecs_enable_component(world, e, Position, false); + test_bool(ecs_is_component_enabled(world, e, Position), true); + ecs_defer_end(world); + + test_bool(ecs_is_component_enabled(world, e, Position), false); + + ecs_fini(world); +} + +static +int compare_position(ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2) { + const Position *p1 = ptr1; + const Position *p2 = ptr2; + return (p1->x > p2->x) - (p1->x < p2->x); +} + +void EnabledComponents_sort() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 2}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {2, 2}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {0, 2}); + + ecs_enable_component(world, e1, Position, true); + ecs_enable_component(world, e2, Position, true); + ecs_enable_component(world, e3, Position, false); + ecs_enable_component(world, e4, Position, false); + + test_bool(ecs_is_component_enabled(world, e1, Position), true); + test_bool(ecs_is_component_enabled(world, e2, Position), true); + test_bool(ecs_is_component_enabled(world, e3, Position), false); + test_bool(ecs_is_component_enabled(world, e4, Position), false); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e2); + test_assert(it.entities[1] == e1); + + /* Entities will have shuffled around, ensure bits got shuffled too */ + test_bool(ecs_is_component_enabled(world, e1, Position), true); + test_bool(ecs_is_component_enabled(world, e2, Position), true); + test_bool(ecs_is_component_enabled(world, e3, Position), false); + test_bool(ecs_is_component_enabled(world, e4, Position), false); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Entity.c b/fggl/ecs2/flecs/test/api/src/Entity.c new file mode 100644 index 0000000000000000000000000000000000000000..c6f7015be7cc2418242cc76c4eff761a749014d3 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Entity.c @@ -0,0 +1,959 @@ +#include <api.h> + +void Entity_init_id() { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){0}); + test_assert(e != 0); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void Entity_init_id_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "foo"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_id_path() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_id_add_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + test_assert(ecs_has(world, e, TagA)); + + ecs_fini(world); +} + +void Entity_init_id_add_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA, TagB} + }); + test_assert(e != 0); + test_assert(ecs_has(world, e, TagA)); + test_assert(ecs_has(world, e, TagB)); + + ecs_fini(world); +} + +void Entity_init_id_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_new_id(world); + test_assert(scope != 0); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){0}); + test_assert(e != 0); + + test_assert(ecs_has_pair(world, e, EcsChildOf, scope)); + + ecs_fini(world); +} + +void Entity_init_id_name_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child" + }); + test_assert(e != 0); + + test_assert(ecs_has_pair(world, e, EcsChildOf, scope)); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_id_path_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child.grandchild" + }); + test_assert(e != 0); + + test_str(ecs_get_name(world, e), "grandchild"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child.grandchild"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_id_name_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .add = {TagA} + }); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, TagA)); + test_str(ecs_get_name(world, e), "foo"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "foo"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_id_name_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .add = {TagA, TagB} + }); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, TagA)); + test_str(ecs_get_name(world, e), "foo"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "foo"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_id_name_2_comp_w_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child", + .add = {TagA, TagB} + }); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, TagA)); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_id_add_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .add = {TagA} + }); + test_assert(r != 0); + test_assert(e == r); + test_assert(ecs_has_id(world, e, TagA)); + + ecs_fini(world); +} + +void Entity_id_add_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .add = {TagA, TagB} + }); + test_assert(r != 0); + test_assert(e == r); + test_assert(ecs_has_id(world, e, TagA)); + test_assert(ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Entity_id_remove_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + ecs_add_id(world, e, TagA); + test_assert(ecs_has_id(world, e, TagA)); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .remove = {TagA} + }); + test_assert(r != 0); + test_assert(e == r); + test_assert(!ecs_has_id(world, e, TagA)); + + ecs_fini(world); +} + +void Entity_id_remove_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + ecs_add_id(world, e, TagA); + ecs_add_id(world, e, TagB); + test_assert(ecs_has_id(world, e, TagA)); + test_assert(ecs_has_id(world, e, TagB)); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .remove = {TagA, TagB} + }); + test_assert(r != 0); + test_assert(e == r); + test_assert(!ecs_has_id(world, e, TagA)); + test_assert(!ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Entity_init_id_path_w_sep() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent::child", + .sep = "::" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_find_id_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_w_existing_id_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t id = ecs_new_id(world); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = id, + .name = "foo" + }); + test_assert(e != 0); + test_assert(e == id); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_name_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_path() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.child" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_path_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child.grandchild" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "grandchild"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child.grandchild"); + ecs_os_free(path); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child.grandchild" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_name_match() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "foo" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_name_match_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + char *path = ecs_get_fullpath(world, e); + test_assert(path != NULL); + test_str(path, "parent.child"); + ecs_os_free(path); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "child" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_set_scope(world, 0); + + r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "parent.child" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_path_match() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "parent.child" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_path_match_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child.grandchild" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "grandchild"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "child.grandchild" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_set_scope(world, 0); + + r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "parent.child.grandchild" + }); + test_assert(r != 0); + test_assert(r == e); + + ecs_fini(world); +} + +void Entity_find_id_name_mismatch() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "bar" + }); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "bar" + }); + test_assert(r == 0); + + ecs_fini(world); +} + +void Entity_find_id_name_mismatch_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "parent" + }); + test_assert(r == 0); + + ecs_fini(world); +} + +void Entity_find_id_path_mismatch() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.child" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "child"); + + ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.foo" + }); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "parent.foo" + }); + test_assert(r == 0); + + ecs_fini(world); +} + +void Entity_find_id_path_mismatch_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + test_assert(scope != 0); + test_str(ecs_get_name(world, scope), "parent"); + + ecs_set_scope(world, scope); + test_int(ecs_get_scope(world), scope); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child.grandchild" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "grandchild"); + + ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "child.foo" + }); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .entity = e, + .name = "child.foo" + }); + test_assert(r == 0); + + ecs_fini(world); +} + +void Entity_find_id_add_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .add = {TagA} + }); + test_assert(r == e); + test_assert(ecs_has_id(world, e, TagA)); + + ecs_fini(world); +} + +void Entity_find_id_add_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .add = {TagA, TagB} + }); + test_assert(r == e); + test_assert(ecs_has_id(world, e, TagA)); + test_assert(ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Entity_find_id_remove_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .add = {TagA} + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + test_assert(ecs_has_id(world, e, TagA)); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .remove = {TagA} + }); + test_assert(r == e); + test_assert(!ecs_has_id(world, e, TagA)); + + ecs_fini(world); +} + +void Entity_find_id_remove_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .add = {TagA, TagB} + }); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + test_assert(ecs_has_id(world, e, TagA)); + test_assert(ecs_has_id(world, e, TagB)); + + ecs_entity_t r = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo", + .remove = {TagA, TagB} + }); + test_assert(r == e); + test_assert(!ecs_has_id(world, e, TagA)); + test_assert(!ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Entity_init_w_scope_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent" + }); + ecs_entity_t foo = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "parent.foo" + }); + + ecs_set_scope(world, foo); + + ecs_entity_t child = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + + test_assert(child != 0); + test_str(ecs_get_name(world, child), "foo"); + + char *path = ecs_get_fullpath(world, child); + test_assert(path != NULL); + test_str(path, "parent.foo.foo"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Entity_init_w_core_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "Prefab" + }); + + test_assert(e != 0); + test_assert(e != EcsPrefab); + + ecs_fini(world); +} + +void Entity_init_w_with() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_set_with(world, Tag); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ }); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, Tag)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void Entity_init_w_with_w_name() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_set_with(world, Tag); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + + test_assert(e != 0); + test_assert(ecs_has_id(world, e, Tag)); + test_str(ecs_get_name(world, e), "foo"); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void Entity_init_w_with_w_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t parent = ecs_new_id(world); + + ecs_set_with(world, Tag); + ecs_set_scope(world, parent); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ }); + test_assert(e != 0); + test_assert(ecs_has_id(world, e, Tag)); + test_assert(ecs_has_id(world, e, ecs_pair(EcsChildOf, parent))); + + test_int(ecs_set_with(world, 0), Tag); + test_int(ecs_set_scope(world, 0), parent); + + ecs_fini(world); +} + +void Entity_init_w_with_w_name_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t parent = ecs_new_id(world); + + ecs_set_with(world, Tag); + ecs_set_scope(world, parent); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "foo" + }); + + test_assert(e != 0); + test_assert(ecs_has_id(world, e, Tag)); + test_assert(ecs_has_id(world, e, ecs_pair(EcsChildOf, parent))); + test_str(ecs_get_name(world, e), "foo"); + + test_int(ecs_set_with(world, 0), Tag); + test_int(ecs_set_scope(world, 0), parent); + + ecs_fini(world); +} + +void Entity_is_valid() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + test_bool(ecs_is_valid(world, e), true); + + ecs_fini(world); +} + +void Entity_is_recycled_valid() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + test_bool(ecs_is_valid(world, e), true); + + ecs_delete(world, e); + test_bool(ecs_is_valid(world, e), false); + + e = ecs_new_id(world); + test_assert(e != 0); + test_assert((uint32_t)e != e); + test_bool(ecs_is_valid(world, e), true); + + ecs_fini(world); +} + +void Entity_is_0_valid() { + ecs_world_t *world = ecs_init(); + + test_bool(ecs_is_valid(world, 0), false); + + ecs_fini(world); +} + +void Entity_is_junk_valid() { + ecs_world_t *world = ecs_init(); + + test_bool(ecs_is_valid(world, 500), true); + test_bool(ecs_is_valid(world, 0xFFFFFFFF), true); + test_bool(ecs_is_valid(world, 0x4DCDCDCDCDCD), true); + + test_bool(ecs_is_alive(world, 500), false); + test_bool(ecs_is_alive(world, 0xFFFFFFFF), false); + test_bool(ecs_is_alive(world, 0x4DCDCDCDCDCD), false); + + test_bool(ecs_is_valid(world, 0xFFFFFFFFFFFFFFFF), false); + test_bool(ecs_is_valid(world, 0xFFFFFFFF00000000), false); + test_bool(ecs_is_valid(world, 0x4DCDCDCDCDCDCD), false); + + ecs_fini(world); +} + +void Entity_is_not_alive_valid() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + test_bool(ecs_is_valid(world, e), true); + test_bool(ecs_is_alive(world, e), true); + + ecs_add_id(world, e, 500); + test_bool(ecs_is_valid(world, e), true); + test_bool(ecs_is_alive(world, e), true); + + ecs_delete(world, e); + test_bool(ecs_is_valid(world, e), false); + test_bool(ecs_is_alive(world, e), false); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Error.c b/fggl/ecs2/flecs/test/api/src/Error.c new file mode 100644 index 0000000000000000000000000000000000000000..c73b154ca833445319f2432a3f40ea5a04a494cf --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Error.c @@ -0,0 +1,105 @@ +#include <api.h> + +void Error_setup() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.abort_ = test_abort; + ecs_os_set_api(&os_api); + ecs_tracing_enable(-5); +} + +void Error_abort() { + test_expect_abort(); + ecs_abort(ECS_INTERNAL_ERROR, NULL); +} + +void Error_abort_w_param() { + test_expect_abort(); + ecs_abort(ECS_INTERNAL_ERROR, "some parameter"); +} + +static bool my_abort_called = false; +static +void my_abort() { + my_abort_called = true; +} + +void Error_override_abort() { + /* The real reason this tests exists is to achieve 100% coverage. Without + * this test, the last line of the 'abort' function would never be covered + * because abort always exits before it gets there. */ + + /* hack, because the setup already set the OS API */ + ((ecs_os_api_t*)&ecs_os_api)->abort_ = my_abort; + _ecs_abort(ECS_INTERNAL_ERROR, __FILE__, __LINE__, NULL); + test_assert(my_abort_called == true); +} + +void Error_assert_true() { + ecs_assert(true, ECS_INTERNAL_ERROR, NULL); + + /* Assert should not trigger */ + test_assert(true); +} + +void Error_assert_false() { + test_expect_abort(); + ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + + /* Assert should not trigger */ + test_assert(false); +} + +void Error_assert_false_w_param() { + test_expect_abort(); + ecs_assert(false, ECS_INTERNAL_ERROR, "some parameter"); + + /* Assert should not trigger */ + test_assert(false); +} + +void Error_error_codes() { + test_assert(ecs_strerror(ECS_INVALID_PARAMETER) != NULL); + test_assert(ecs_strerror(ECS_NOT_A_COMPONENT) != NULL); + test_assert(ecs_strerror(ECS_TYPE_NOT_AN_ENTITY) != NULL); + test_assert(ecs_strerror(ECS_INTERNAL_ERROR) != NULL); + test_assert(ecs_strerror(ECS_ALREADY_DEFINED) != NULL); + test_assert(ecs_strerror(ECS_INVALID_COMPONENT_SIZE) != NULL); + test_assert(ecs_strerror(ECS_INVALID_COMPONENT_ALIGNMENT) != NULL); + test_assert(ecs_strerror(ECS_OUT_OF_MEMORY) != NULL); + test_assert(ecs_strerror(ECS_MODULE_UNDEFINED) != NULL); + test_assert(ecs_strerror(ECS_COLUMN_INDEX_OUT_OF_RANGE) != NULL); + test_assert(ecs_strerror(ECS_COLUMN_IS_NOT_SHARED) != NULL); + test_assert(ecs_strerror(ECS_COLUMN_IS_SHARED) != NULL); + test_assert(ecs_strerror(ECS_COLUMN_HAS_NO_DATA) != NULL); + test_assert(ecs_strerror(ECS_COLUMN_TYPE_MISMATCH) != NULL); + test_assert(ecs_strerror(ECS_INVALID_WHILE_ITERATING) != NULL); + test_assert(ecs_strerror(ECS_INVALID_FROM_WORKER) != NULL); + test_assert(ecs_strerror(ECS_OUT_OF_RANGE) != NULL); + test_assert(ecs_strerror(ECS_THREAD_ERROR) != NULL); + test_assert(ecs_strerror(ECS_MISSING_OS_API) != NULL); + test_assert(ecs_strerror(ECS_UNSUPPORTED) != NULL); + test_assert(ecs_strerror(ECS_NO_OUT_COLUMNS) != NULL); + test_assert(ecs_strerror(ECS_COLUMN_ACCESS_VIOLATION) != NULL); + test_assert(ecs_strerror(ECS_DESERIALIZE_FORMAT_ERROR) != NULL); +} + +void Error_log_dbg() { + ecs_os_dbg("test debug message"); + test_assert(true); +} + +void Error_log_log() { + ecs_os_log("test log message"); + test_assert(true); +} + +void Error_log_warning() { + ecs_os_warn("test warning message"); + test_assert(true); +} + +void Error_log_error() { + ecs_os_err("test error message"); + test_assert(true); +} diff --git a/fggl/ecs2/flecs/test/api/src/Filter.c b/fggl/ecs2/flecs/test/api/src/Filter.c new file mode 100644 index 0000000000000000000000000000000000000000..c68ef6cab42c82a648c0ae70138cc145d73c5ab1 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Filter.c @@ -0,0 +1,2279 @@ +#include <api.h> + +void Filter_filter_1_term() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{TagA}} + }); + + test_int(f.term_count, 1); + test_int(f.term_count_actual, 1); + test_assert(f.terms != NULL); + test_int(f.terms[0].id, TagA); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_2_terms() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{TagA}, {TagB}} + }); + + test_int(f.term_count, 2); + test_int(f.term_count_actual, 2); + test_assert(f.terms != NULL); + + test_int(f.terms[0].id, TagA); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + test_int(f.terms[1].id, TagB); + test_int(f.terms[1].oper, EcsAnd); + test_int(f.terms[1].index, 1); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_3_terms() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{TagA}, {TagB}, {TagC}} + }); + + test_int(f.term_count, 3); + test_int(f.term_count_actual, 3); + test_assert(f.terms != NULL); + + test_int(f.terms[0].id, TagA); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + + test_int(f.terms[1].id, TagB); + test_int(f.terms[1].oper, EcsAnd); + test_int(f.terms[1].index, 1); + + test_int(f.terms[2].id, TagC); + test_int(f.terms[2].oper, EcsAnd); + test_int(f.terms[2].index, 2); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_3_terms_w_or() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{TagA}, {TagB, .oper = EcsOr}, {TagC, .oper = EcsOr}} + }); + + test_int(f.term_count, 3); + test_int(f.term_count_actual, 2); + test_assert(f.terms != NULL); + + test_int(f.terms[0].id, TagA); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + + test_int(f.terms[1].id, TagB); + test_int(f.terms[1].oper, EcsOr); + test_int(f.terms[1].index, 1); + + test_int(f.terms[2].id, TagC); + test_int(f.terms[2].oper, EcsOr); + test_int(f.terms[2].index, 1); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_4_terms_w_or_at_1() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{TagA}, {TagB, .oper = EcsOr}, {TagC, .oper = EcsOr}, {TagD}} + }); + + test_int(f.term_count, 4); + test_int(f.term_count_actual, 3); + test_assert(f.terms != NULL); + + test_int(f.terms[0].id, TagA); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + + test_int(f.terms[1].id, TagB); + test_int(f.terms[1].oper, EcsOr); + test_int(f.terms[1].index, 1); + + test_int(f.terms[2].id, TagC); + test_int(f.terms[2].oper, EcsOr); + test_int(f.terms[2].index, 1); + + test_int(f.terms[3].id, TagD); + test_int(f.terms[3].oper, EcsAnd); + test_int(f.terms[3].index, 2); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_w_pair_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + ecs_id_t pair = ecs_pair(Rel, Obj); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{pair}} + }); + + test_int(f.term_count, 1); + test_int(f.term_count_actual, 1); + test_assert(f.terms != NULL); + test_int(f.terms[0].id, pair); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_w_pred_obj() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + ecs_id_t pair = ecs_pair(Rel, Obj); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{.pred.entity = Rel, .args[1].entity = Obj}} + }); + + test_int(f.term_count, 1); + test_int(f.term_count_actual, 1); + test_assert(f.terms != NULL); + test_int(f.terms[0].id, pair); + test_int(f.terms[0].oper, EcsAnd); + test_int(f.terms[0].index, 0); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_term_w_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_term_t term = { + .id = Tag + }; + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, Tag); + test_int(term.pred.entity, Tag); + test_int(term.args[0].entity, EcsThis); + test_int(term.args[1].entity, 0); + + ecs_fini(world); +} + +void Filter_term_w_pair_id() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + ecs_id_t pair = ecs_pair(Rel, Obj); + + ecs_term_t term = { + .id = pair + }; + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, pair); + test_int(term.pred.entity, Rel); + test_int(term.args[0].entity, EcsThis); + test_int(term.args[1].entity, Obj); + + ecs_fini(world); +} + +void Filter_term_w_pred_obj() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + ecs_id_t pair = ecs_pair(Rel, Obj); + + ecs_term_t term = { + .pred.entity = Rel, + .args[1].entity = Obj + }; + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, pair); + test_int(term.pred.entity, Rel); + test_int(term.args[1].entity, Obj); + + ecs_fini(world); +} + +void Filter_term_w_pair_finalize_twice() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + ecs_id_t pair = ecs_pair(Rel, Obj); + + ecs_term_t term = { + .pred.entity = Rel, + .args[1].entity = Obj + }; + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, pair); + test_int(term.pred.entity, Rel); + test_int(term.args[1].entity, Obj); + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, pair); + test_int(term.pred.entity, Rel); + test_int(term.args[1].entity, Obj); + + ecs_fini(world); +} + +void Filter_term_w_role() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_term_t term = { + .id = Tag, + .role = ECS_SWITCH + }; + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, Tag | ECS_SWITCH); + test_int(term.role, ECS_SWITCH); + test_int(term.pred.entity, Tag); + + ecs_fini(world); +} + +void Filter_term_w_pred_role() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + ecs_term_t term = { + .pred.entity = Tag, + .role = ECS_SWITCH + }; + + test_assert(ecs_term_finalize(world, NULL, NULL, &term) == 0); + test_int(term.id, Tag | ECS_SWITCH); + test_int(term.role, ECS_SWITCH); + test_int(term.pred.entity, Tag); + + ecs_fini(world); +} + +void Filter_filter_move() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_filter_t f_1; + test_int(ecs_filter_init(world, &f_1, &(ecs_filter_desc_t) { + .terms = {{TagA}, {TagB}} + }), 0); + + test_int(f_1.term_count, 2); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + + ecs_filter_t f_2 = {}; + ecs_filter_move(&f_2, &f_1); + + test_int(f_2.term_count, 2); + test_assert(f_2.terms != NULL); + test_int(f_2.terms[0].id, TagA); + test_int(f_2.terms[1].id, TagB); + + test_int(f_1.term_count, 0); + test_assert(f_1.terms == NULL); + + ecs_filter_fini(&f_1); + ecs_filter_fini(&f_2); + + ecs_fini(world); +} + +void Filter_filter_copy() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_filter_t f_1; + test_int(ecs_filter_init(world, &f_1, &(ecs_filter_desc_t) { + .terms = {{TagA}, {TagB}} + }), 0); + + test_int(f_1.term_count, 2); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + + ecs_filter_t f_2 = {}; + ecs_filter_copy(&f_2, &f_1); + + test_int(f_1.term_count, 2); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + + test_int(f_2.term_count, 2); + test_assert(f_2.terms != NULL); + test_int(f_2.terms[0].id, TagA); + test_int(f_2.terms[1].id, TagB); + + test_assert(f_1.terms != f_2.terms); + + ecs_filter_fini(&f_1); + ecs_filter_fini(&f_2); + + ecs_fini(world); +} + +void Filter_filter_w_resources_copy() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_filter_t f_1; + test_int(ecs_filter_init(world, &f_1, &(ecs_filter_desc_t) { + .terms = {{TagA, .args[0].name = "This"}, {TagB, .args[0].name = "This"}} + }), 0); + + test_int(f_1.term_count, 2); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + + ecs_filter_t f_2 = {}; + ecs_filter_copy(&f_2, &f_1); + + test_int(f_1.term_count, 2); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + + test_int(f_2.term_count, 2); + test_assert(f_2.terms != NULL); + test_int(f_2.terms[0].id, TagA); + test_int(f_2.terms[1].id, TagB); + + test_assert(f_1.terms != f_2.terms); + + ecs_filter_fini(&f_1); + ecs_filter_fini(&f_2); + + ecs_fini(world); +} + +void Filter_filter_w_10_terms() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + + ecs_filter_t f_1; + test_int(ecs_filter_init(world, &f_1, &(ecs_filter_desc_t) { + .terms = { + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ} + } + }), 0); + + test_int(f_1.term_count, 10); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + test_int(f_1.terms[2].id, TagC); + test_int(f_1.terms[3].id, TagD); + test_int(f_1.terms[4].id, TagE); + test_int(f_1.terms[5].id, TagF); + test_int(f_1.terms[6].id, TagG); + test_int(f_1.terms[7].id, TagH); + test_int(f_1.terms[8].id, TagI); + test_int(f_1.terms[9].id, TagJ); + + ecs_entity_t e = ecs_new_id(world); + ecs_add(world, e, TagA); + ecs_add(world, e, TagB); + ecs_add(world, e, TagC); + ecs_add(world, e, TagD); + ecs_add(world, e, TagE); + ecs_add(world, e, TagF); + ecs_add(world, e, TagG); + ecs_add(world, e, TagH); + ecs_add(world, e, TagI); + ecs_add(world, e, TagJ); + + ecs_iter_t it = ecs_filter_iter(world, &f_1); + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e); + test_int(it.ids[0], TagA); + test_int(it.ids[1], TagB); + test_int(it.ids[2], TagC); + test_int(it.ids[3], TagD); + test_int(it.ids[4], TagE); + test_int(it.ids[5], TagF); + test_int(it.ids[6], TagG); + test_int(it.ids[7], TagH); + test_int(it.ids[8], TagI); + test_int(it.ids[9], TagJ); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f_1); + + ecs_fini(world); +} + +void Filter_filter_w_10_terms_move() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + + ecs_filter_t f_1; + test_int(ecs_filter_init(world, &f_1, &(ecs_filter_desc_t) { + .terms = { + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ} + } + }), 0); + + test_int(f_1.term_count, 10); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + test_int(f_1.terms[2].id, TagC); + test_int(f_1.terms[3].id, TagD); + test_int(f_1.terms[4].id, TagE); + test_int(f_1.terms[5].id, TagF); + test_int(f_1.terms[6].id, TagG); + test_int(f_1.terms[7].id, TagH); + test_int(f_1.terms[8].id, TagI); + test_int(f_1.terms[9].id, TagJ); + + ecs_filter_t f_2 = {}; + ecs_filter_move(&f_2, &f_1); + + test_int(f_2.term_count, 10); + test_assert(f_2.terms != NULL); + test_int(f_2.terms[0].id, TagA); + test_int(f_2.terms[1].id, TagB); + test_int(f_2.terms[2].id, TagC); + test_int(f_2.terms[3].id, TagD); + test_int(f_2.terms[4].id, TagE); + test_int(f_2.terms[5].id, TagF); + test_int(f_2.terms[6].id, TagG); + test_int(f_2.terms[7].id, TagH); + test_int(f_2.terms[8].id, TagI); + test_int(f_2.terms[9].id, TagJ); + + test_int(f_1.term_count, 0); + test_assert(f_1.terms == NULL); + + ecs_filter_fini(&f_1); + ecs_filter_fini(&f_2); + + ecs_fini(world); +} + +void Filter_filter_w_10_terms_copy() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + + ecs_filter_t f_1; + test_int(ecs_filter_init(world, &f_1, &(ecs_filter_desc_t) { + .terms = { + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ} + } + }), 0); + + test_int(f_1.term_count, 10); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + test_int(f_1.terms[2].id, TagC); + test_int(f_1.terms[3].id, TagD); + test_int(f_1.terms[4].id, TagE); + test_int(f_1.terms[5].id, TagF); + test_int(f_1.terms[6].id, TagG); + test_int(f_1.terms[7].id, TagH); + test_int(f_1.terms[8].id, TagI); + test_int(f_1.terms[9].id, TagJ); + + ecs_filter_t f_2 = {}; + ecs_filter_copy(&f_2, &f_1); + + test_int(f_1.term_count, 10); + test_assert(f_1.terms != NULL); + test_int(f_1.terms[0].id, TagA); + test_int(f_1.terms[1].id, TagB); + test_int(f_1.terms[2].id, TagC); + test_int(f_1.terms[3].id, TagD); + test_int(f_1.terms[4].id, TagE); + test_int(f_1.terms[5].id, TagF); + test_int(f_1.terms[6].id, TagG); + test_int(f_1.terms[7].id, TagH); + test_int(f_1.terms[8].id, TagI); + test_int(f_1.terms[9].id, TagJ); + + test_int(f_2.term_count, 10); + test_assert(f_2.terms != NULL); + test_int(f_2.terms[0].id, TagA); + test_int(f_2.terms[1].id, TagB); + test_int(f_2.terms[2].id, TagC); + test_int(f_2.terms[3].id, TagD); + test_int(f_2.terms[4].id, TagE); + test_int(f_2.terms[5].id, TagF); + test_int(f_2.terms[6].id, TagG); + test_int(f_2.terms[7].id, TagH); + test_int(f_2.terms[8].id, TagI); + test_int(f_2.terms[9].id, TagJ); + + test_assert(f_1.terms != f_2.terms); + + ecs_filter_fini(&f_1); + ecs_filter_fini(&f_2); + + ecs_fini(world); +} + +void Filter_term_iter_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t e_2 = ecs_set(world, 0, Position, {3, 4}); + ecs_entity_t e_3 = ecs_set(world, 0, Position, {5, 6}); + + ecs_add(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + ecs_id(Position) + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p[0].x, 1); + test_int(p[0].y, 2); + + test_int(p[1].x, 3); + test_int(p[1].y, 4); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + + p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p[0].x, 5); + test_int(p[0].y, 6); + + ecs_fini(world); +} + +void Filter_term_iter_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag1); + ECS_TAG(world, Tag2); + + ecs_entity_t e_1 = ecs_new(world, Tag1); + ecs_entity_t e_2 = ecs_new(world, Tag1); + ecs_entity_t e_3 = ecs_new(world, Tag1); + + ecs_add(world, e_3, Tag2); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { Tag1 }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), Tag1); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), Tag1); + + ecs_fini(world); +} + +void Filter_term_iter_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_2 = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_3 = ecs_new_w_pair(world, Rel, Obj); + + ecs_add(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { ecs_pair(Rel, Obj) }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + ecs_fini(world); +} + +void Filter_term_iter_pair_w_rel_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel_1); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, Rel_1, Obj); + ecs_entity_t e_2 = ecs_new_w_pair(world, Rel_1, Obj); + ecs_entity_t e_3 = ecs_new_w_pair(world, Rel_2, Obj); + + ecs_add(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + ecs_pair(EcsWildcard, Obj) + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_1, Obj)); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + ecs_fini(world); +} + +void Filter_term_iter_pair_w_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, Rel, Obj_1); + ecs_entity_t e_2 = ecs_new_w_pair(world, Rel, Obj_1); + ecs_entity_t e_3 = ecs_new_w_pair(world, Rel, Obj_2); + + ecs_add(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + ecs_pair(Rel, EcsWildcard) + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_1)); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_2)); + + ecs_fini(world); +} + +void Filter_term_iter_w_superset() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t base = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsIsA, base); + + ecs_add_id(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_id(Position), + .args[0].set = { + .mask = EcsSuperSet + } + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), base); + + Position *ptr = ecs_term(&it, Position, 1); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), base); + + test_assert(ptr == ecs_term(&it, Position, 1)); + + ecs_fini(world); +} + +void Filter_term_iter_w_superset_childof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t parent = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_add_id(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_id(Position), + .args[0].set = { + .relation = EcsChildOf, + .mask = EcsSuperSet + } + }); + + { + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), parent); + + Position *ptr = ecs_term(&it, Position, 1); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + } + + { + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), parent); + + Position *ptr = ecs_term(&it, Position, 1); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + } + + ecs_fini(world); +} + +void Filter_term_iter_w_superset_self() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t base = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e_0 = ecs_set(world, 0, Position, {11, 22}); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsIsA, base); + + ecs_add_id(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_id(Position), + .args[0].set = { + .mask = EcsSelf | EcsSuperSet + } + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], base); + test_int(it.entities[1], e_0); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), 0); + + Position *ptr = ecs_term(&it, Position, 1); + test_int(ptr[0].x, 10); + test_int(ptr[0].y, 20); + test_int(ptr[1].x, 11); + test_int(ptr[1].y, 22); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), base); + + ptr = ecs_term(&it, Position, 1); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), base); + + test_assert(ptr == ecs_term(&it, Position, 1)); + + ecs_fini(world); +} + +void Filter_term_iter_w_superset_self_childof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t parent = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e_0 = ecs_set(world, 0, Position, {11, 22}); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsIsA, parent); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsIsA, parent); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsIsA, parent); + + ecs_add_id(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_id(Position), + .args[0].set = { + .mask = EcsSelf | EcsSuperSet + } + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], parent); + test_int(it.entities[1], e_0); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), 0); + + Position *ptr = ecs_term(&it, Position, 1); + test_int(ptr[0].x, 10); + test_int(ptr[0].y, 20); + test_int(ptr[1].x, 11); + test_int(ptr[1].y, 22); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), parent); + + ptr = ecs_term(&it, Position, 1); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), parent); + + test_assert(ptr == ecs_term(&it, Position, 1)); + + ecs_fini(world); +} + +void Filter_term_iter_w_superset_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t base = ecs_new(world, TagA); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsIsA, base); + + ecs_add_id(world, e_3, TagB); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = TagA, + .args[0].set = { + .mask = EcsSuperSet + } + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_source(&it, 1), base); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_source(&it, 1), base); + + ecs_fini(world); +} + +void Filter_term_iter_w_superset_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + ECS_TAG(world, Tag); + + ecs_entity_t base = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsIsA, base); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsIsA, base); + + ecs_add_id(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_pair(Rel, Obj), + .args[0].set = { + .mask = EcsSuperSet + } + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), base); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), base); + + ecs_fini(world); +} + +void Filter_term_iter_w_superset_pair_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + ECS_TAG(world, Tag); + + ecs_entity_t base_1 = ecs_new_w_pair(world, Rel, Obj_1); + ecs_entity_t base_2 = ecs_new_w_pair(world, Rel, Obj_2); + ecs_entity_t e_1 = ecs_new_w_pair(world, EcsIsA, base_1); + ecs_entity_t e_2 = ecs_new_w_pair(world, EcsIsA, base_1); + ecs_entity_t e_3 = ecs_new_w_pair(world, EcsIsA, base_2); + ecs_entity_t e_4 = ecs_new_w_pair(world, EcsIsA, base_2); + + ecs_add_id(world, e_3, Tag); + + ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { + .id = ecs_pair(Rel, EcsWildcard), + .args[0].set = { + .mask = EcsSuperSet + } + }); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_2)); + test_int(ecs_term_source(&it, 1), base_2); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_1)); + test_int(ecs_term_source(&it, 1), base_1); + + test_assert(ecs_term_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_2)); + test_int(ecs_term_source(&it, 1), base_2); + + ecs_fini(world); +} + +void Filter_term_iter_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, TagB); + + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_add(world, e2, TagB); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + test_assert(stage != NULL); + + ecs_iter_t it = ecs_term_iter(stage, &(ecs_term_t) { .id = Tag }); + test_assert(it.real_world == world); + test_assert(it.world == stage); + + test_bool(ecs_term_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(it.ids[0], Tag); + + test_bool(ecs_term_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e2); + test_int(it.ids[0], Tag); + + ecs_staging_end(world); + + ecs_fini(world); +} + +void Filter_filter_iter_1_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_entity_t e_3 = ecs_new(world, TagA); + + ecs_add_id(world, e_3, TagB); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ TagA }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_2_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_entity_t e_3 = ecs_new(world, TagA); + + ecs_new(world, TagA); /* Non matching entity */ + + ecs_add_id(world, e_1, TagB); + ecs_add_id(world, e_2, TagB); + ecs_add_id(world, e_3, TagB); + + ecs_add_id(world, e_3, TagC); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ TagA }, { TagB }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_2_tags_1_not() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_entity_t e_3 = ecs_new(world, TagA); + + ecs_add_id(world, e_3, TagC); + + ecs_entity_t e_4 = ecs_new(world, TagA); /* Non matching entity */ + ecs_add_id(world, e_4, TagB); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ TagA }, { TagB, .oper = EcsNot }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_3_tags_2_or() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_entity_t e_3 = ecs_new(world, TagA); + + ecs_add_id(world, e_1, TagB); + ecs_add_id(world, e_2, TagB); + ecs_add_id(world, e_3, TagC); + + ecs_entity_t e_4 = ecs_new(world, TagA); /* Non matching entity */ + ecs_add_id(world, e_4, TagD); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ TagA }, { TagB, .oper = EcsOr }, { TagC, .oper = EcsOr }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagC); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, TagB); + + ecs_entity_t e_1 = ecs_set(world, 0, Position, {10, 21}); + ecs_entity_t e_2 = ecs_set(world, 0, Position, {12, 23}); + ecs_entity_t e_3 = ecs_set(world, 0, Position, {14, 25}); + + ecs_add_id(world, e_3, TagB); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), 0); + + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p[0].x, 10); + test_int(p[0].y, 21); + test_int(p[1].x, 12); + test_int(p[1].y, 23); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), 0); + + p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p[0].x, 14); + test_int(p[0].y, 25); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_2_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, TagB); + + ecs_entity_t e_1 = ecs_set(world, 0, Position, {10, 21}); + ecs_entity_t e_2 = ecs_set(world, 0, Position, {12, 23}); + ecs_entity_t e_3 = ecs_set(world, 0, Position, {14, 25}); + + ecs_set(world, e_1, Velocity, {0, 1}); + ecs_set(world, e_2, Velocity, {2, 3}); + ecs_set(world, e_3, Velocity, {4, 5}); + + ecs_add_id(world, e_3, TagB); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }, { ecs_id(Velocity) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + { + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), 0); + + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p[0].x, 14); + test_int(p[0].y, 25); + + Velocity *v = ecs_term(&it, Velocity, 2); + test_assert(v != NULL); + test_int(v[0].x, 4); + test_int(v[0].y, 5); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(Position)); + test_int(ecs_term_source(&it, 1), 0); + } + + { + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p[0].x, 10); + test_int(p[0].y, 21); + test_int(p[1].x, 12); + test_int(p[1].y, 23); + + Velocity *v = ecs_term(&it, Velocity, 2); + test_assert(v != NULL); + test_int(v[0].x, 0); + test_int(v[0].y, 1); + test_int(v[1].x, 2); + test_int(v[1].y, 3); + } + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_pair_id() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_2 = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_3 = ecs_new_w_pair(world, Rel, Obj); + + ecs_add_id(world, e_3, Tag); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_pair(Rel, EcsWildcard) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_2_pair_ids() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, ObjA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjB); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, RelA, ObjA); + ecs_entity_t e_2 = ecs_new_w_pair(world, RelA, ObjA); + ecs_entity_t e_3 = ecs_new_w_pair(world, RelA, ObjA); + + ecs_add_pair(world, e_1, RelB, ObjB); + ecs_add_pair(world, e_2, RelB, ObjB); + ecs_add_pair(world, e_3, RelB, ObjB); + + ecs_add_id(world, e_3, Tag); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + { ecs_pair(RelA, ObjA) }, + { ecs_pair(RelB, EcsWildcard) } + } + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(RelA, ObjA)); + test_int(ecs_term_id(&it, 2), ecs_pair(RelB, ObjB)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(RelA, ObjA)); + test_int(ecs_term_id(&it, 2), ecs_pair(RelB, ObjB)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_pair_pred_obj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_2 = ecs_new_w_pair(world, Rel, Obj); + ecs_entity_t e_3 = ecs_new_w_pair(world, Rel, Obj); + + ecs_add_id(world, e_3, Tag); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ .pred.entity = Rel, .args[1].entity = EcsWildcard }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_pair_2_pred_obj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, ObjA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjB); + ECS_TAG(world, Tag); + + ecs_entity_t e_1 = ecs_new_w_pair(world, RelA, ObjA); + ecs_entity_t e_2 = ecs_new_w_pair(world, RelA, ObjA); + ecs_entity_t e_3 = ecs_new_w_pair(world, RelA, ObjA); + + ecs_add_pair(world, e_1, RelB, ObjB); + ecs_add_pair(world, e_2, RelB, ObjB); + ecs_add_pair(world, e_3, RelB, ObjB); + + ecs_add_id(world, e_3, Tag); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + { .pred.entity = RelA, .args[1].entity = ObjA }, + { .pred.entity = RelB, .args[1].entity = EcsWildcard } + } + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), ecs_pair(RelA, ObjA)); + test_int(ecs_term_id(&it, 2), ecs_pair(RelB, ObjB)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), ecs_pair(RelA, ObjA)); + test_int(ecs_term_id(&it, 2), ecs_pair(RelB, ObjB)); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_null() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagB); + ecs_entity_t e_3 = ecs_new(world, TagB); + ecs_add_id(world, e_3, TagC); + + bool e_1_found = false; + bool e_2_found = false; + bool e_3_found = false; + + ecs_iter_t it = ecs_filter_iter(world, NULL); + + while (ecs_filter_next(&it)) { + int i; + for (i = 0; i < it.count; i ++) { + if (it.entities[i] == e_1) { + test_bool(e_1_found, false); + e_1_found = true; + } else + if (it.entities[i] == e_2) { + test_bool(e_2_found, false); + e_2_found = true; + } else + if (it.entities[i] == e_3) { + test_bool(e_3_found, false); + e_3_found = true; + } + } + } + + test_bool (e_1_found, true); + test_bool (e_2_found, true); + test_bool (e_3_found, true); + + ecs_fini(world); +} + +void Filter_filter_iter_1_not_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagB); + ecs_entity_t e_3 = ecs_new(world, TagB); + ecs_add_id(world, e_3, TagC); + + bool e_1_found = false; + bool e_2_found = false; + bool e_3_found = false; + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ .id = TagC, .oper = EcsNot }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + while (ecs_filter_next(&it)) { + int i; + for (i = 0; i < it.count; i ++) { + if (it.entities[i] == e_1) { + test_bool(e_1_found, false); + e_1_found = true; + } else + if (it.entities[i] == e_2) { + test_bool(e_2_found, false); + e_2_found = true; + } else + if (it.entities[i] == e_3) { + e_3_found = true; + } + } + } + + test_bool (e_1_found, true); + test_bool (e_2_found, true); + test_bool (e_3_found, false); + + ecs_fini(world); +} + +void Filter_filter_iter_2_tags_1_optional() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_entity_t e_3 = ecs_new(world, TagA); + + ecs_add_id(world, e_3, TagB); + + ecs_entity_t e_4 = ecs_new(world, TagB); /* Non matching entity */ + ecs_add_id(world, e_4, TagC); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ TagA }, { TagB, .oper = EcsOptional }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e_1); + test_int(it.entities[1], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_3); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), true); + test_int(ecs_term_source(&it, 1), 0); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + test_assert(stage != NULL); + + ecs_filter_t f; + test_int(ecs_filter_init(stage, &f, &(ecs_filter_desc_t) { + .terms = {{ Tag }} + }), 0); + + ecs_iter_t it = ecs_filter_iter(stage, &f); + + test_assert(it.real_world == world); + test_assert(it.world == stage); + test_bool(ecs_filter_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e); + test_int(it.ids[0], Tag); + + ecs_staging_end(world); + + ecs_fini(world); +} + +void Filter_filter_iter_10_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + ECS_TAG(world, TagK); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_add_id(world, e_1, TagB); + ecs_add_id(world, e_1, TagC); + ecs_add_id(world, e_1, TagD); + ecs_add_id(world, e_1, TagE); + ecs_add_id(world, e_1, TagF); + ecs_add_id(world, e_1, TagG); + ecs_add_id(world, e_1, TagH); + ecs_add_id(world, e_1, TagI); + ecs_add_id(world, e_1, TagJ); + + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_add_id(world, e_2, TagB); + ecs_add_id(world, e_2, TagC); + ecs_add_id(world, e_2, TagD); + ecs_add_id(world, e_2, TagE); + ecs_add_id(world, e_2, TagF); + ecs_add_id(world, e_2, TagG); + ecs_add_id(world, e_2, TagH); + ecs_add_id(world, e_2, TagI); + ecs_add_id(world, e_2, TagJ); + ecs_add_id(world, e_2, TagK); /* 2nd match in different table */ + + ecs_filter_t f; + test_int(ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ} + } + }), 0); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_20_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + ECS_TAG(world, TagK); + ECS_TAG(world, TagL); + ECS_TAG(world, TagM); + ECS_TAG(world, TagN); + ECS_TAG(world, TagO); + ECS_TAG(world, TagP); + ECS_TAG(world, TagQ); + ECS_TAG(world, TagR); + ECS_TAG(world, TagS); + ECS_TAG(world, TagT); + ECS_TAG(world, TagU); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_add_id(world, e_1, TagB); + ecs_add_id(world, e_1, TagC); + ecs_add_id(world, e_1, TagD); + ecs_add_id(world, e_1, TagE); + ecs_add_id(world, e_1, TagF); + ecs_add_id(world, e_1, TagG); + ecs_add_id(world, e_1, TagH); + ecs_add_id(world, e_1, TagI); + ecs_add_id(world, e_1, TagJ); + ecs_add_id(world, e_1, TagK); + ecs_add_id(world, e_1, TagL); + ecs_add_id(world, e_1, TagM); + ecs_add_id(world, e_1, TagN); + ecs_add_id(world, e_1, TagO); + ecs_add_id(world, e_1, TagP); + ecs_add_id(world, e_1, TagQ); + ecs_add_id(world, e_1, TagR); + ecs_add_id(world, e_1, TagS); + ecs_add_id(world, e_1, TagT); + + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_add_id(world, e_2, TagB); + ecs_add_id(world, e_2, TagC); + ecs_add_id(world, e_2, TagD); + ecs_add_id(world, e_2, TagE); + ecs_add_id(world, e_2, TagF); + ecs_add_id(world, e_2, TagG); + ecs_add_id(world, e_2, TagH); + ecs_add_id(world, e_2, TagI); + ecs_add_id(world, e_2, TagJ); + ecs_add_id(world, e_2, TagK); + ecs_add_id(world, e_2, TagL); + ecs_add_id(world, e_2, TagM); + ecs_add_id(world, e_2, TagN); + ecs_add_id(world, e_2, TagO); + ecs_add_id(world, e_2, TagP); + ecs_add_id(world, e_2, TagQ); + ecs_add_id(world, e_2, TagR); + ecs_add_id(world, e_2, TagS); + ecs_add_id(world, e_2, TagT); + ecs_add_id(world, e_2, TagU); /* 2nd match in different table */ + + ecs_filter_t f; + test_int(ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms_buffer = (ecs_term_t[]){ + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ}, {TagK}, {TagL}, {TagM}, {TagN}, {TagO}, {TagP}, + {TagQ}, {TagR}, {TagS}, {TagT} + }, + .terms_buffer_count = 20 + }), 0); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + test_int(ecs_term_id(&it, 11), TagK); + test_int(ecs_term_id(&it, 12), TagL); + test_int(ecs_term_id(&it, 13), TagM); + test_int(ecs_term_id(&it, 14), TagN); + test_int(ecs_term_id(&it, 15), TagO); + test_int(ecs_term_id(&it, 16), TagP); + test_int(ecs_term_id(&it, 17), TagQ); + test_int(ecs_term_id(&it, 18), TagR); + test_int(ecs_term_id(&it, 19), TagS); + test_int(ecs_term_id(&it, 20), TagT); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + test_int(ecs_term_id(&it, 11), TagK); + test_int(ecs_term_id(&it, 12), TagL); + test_int(ecs_term_id(&it, 13), TagM); + test_int(ecs_term_id(&it, 14), TagN); + test_int(ecs_term_id(&it, 15), TagO); + test_int(ecs_term_id(&it, 16), TagP); + test_int(ecs_term_id(&it, 17), TagQ); + test_int(ecs_term_id(&it, 18), TagR); + test_int(ecs_term_id(&it, 19), TagS); + test_int(ecs_term_id(&it, 20), TagT); + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +typedef struct { + float v; +} CompA, CompB, CompC, CompD, CompE, CompF, CompG, CompH, CompI, CompJ, CompK, + CompL, CompM, CompN, CompO, CompP, CompQ, CompR, CompS, CompT, CompU; + +void Filter_filter_iter_10_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, CompA); + ECS_COMPONENT(world, CompB); + ECS_COMPONENT(world, CompC); + ECS_COMPONENT(world, CompD); + ECS_COMPONENT(world, CompE); + ECS_COMPONENT(world, CompF); + ECS_COMPONENT(world, CompG); + ECS_COMPONENT(world, CompH); + ECS_COMPONENT(world, CompI); + ECS_COMPONENT(world, CompJ); + ECS_COMPONENT(world, CompK); + + ecs_entity_t e_1 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_1, CompB, {10}); + ecs_set(world, e_1, CompC, {10}); + ecs_set(world, e_1, CompD, {10}); + ecs_set(world, e_1, CompE, {10}); + ecs_set(world, e_1, CompF, {10}); + ecs_set(world, e_1, CompG, {10}); + ecs_set(world, e_1, CompH, {10}); + ecs_set(world, e_1, CompI, {10}); + ecs_set(world, e_1, CompJ, {10}); + + ecs_entity_t e_2 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_2, CompB, {10}); + ecs_set(world, e_2, CompC, {10}); + ecs_set(world, e_2, CompD, {10}); + ecs_set(world, e_2, CompE, {10}); + ecs_set(world, e_2, CompF, {10}); + ecs_set(world, e_2, CompG, {10}); + ecs_set(world, e_2, CompH, {10}); + ecs_set(world, e_2, CompI, {10}); + ecs_set(world, e_2, CompJ, {10}); + ecs_set(world, e_2, CompK, {10}); + + ecs_filter_t f; + test_int(ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = { + {ecs_id(CompA)}, {ecs_id(CompB)}, {ecs_id(CompC)}, {ecs_id(CompD)}, + {ecs_id(CompE)}, {ecs_id(CompF)}, {ecs_id(CompG)}, {ecs_id(CompH)}, + {ecs_id(CompI)}, {ecs_id(CompJ)} + } + }), 0); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + + int i; + for (i = 0; i < 10; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + + for (i = 0; i < 10; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Filter_filter_iter_20_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, CompA); + ECS_COMPONENT(world, CompB); + ECS_COMPONENT(world, CompC); + ECS_COMPONENT(world, CompD); + ECS_COMPONENT(world, CompE); + ECS_COMPONENT(world, CompF); + ECS_COMPONENT(world, CompG); + ECS_COMPONENT(world, CompH); + ECS_COMPONENT(world, CompI); + ECS_COMPONENT(world, CompJ); + ECS_COMPONENT(world, CompK); + ECS_COMPONENT(world, CompL); + ECS_COMPONENT(world, CompM); + ECS_COMPONENT(world, CompN); + ECS_COMPONENT(world, CompO); + ECS_COMPONENT(world, CompP); + ECS_COMPONENT(world, CompQ); + ECS_COMPONENT(world, CompR); + ECS_COMPONENT(world, CompS); + ECS_COMPONENT(world, CompT); + ECS_COMPONENT(world, CompU); + + ecs_entity_t e_1 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_1, CompB, {10}); + ecs_set(world, e_1, CompC, {10}); + ecs_set(world, e_1, CompD, {10}); + ecs_set(world, e_1, CompE, {10}); + ecs_set(world, e_1, CompF, {10}); + ecs_set(world, e_1, CompG, {10}); + ecs_set(world, e_1, CompH, {10}); + ecs_set(world, e_1, CompI, {10}); + ecs_set(world, e_1, CompJ, {10}); + ecs_set(world, e_1, CompK, {10}); + ecs_set(world, e_1, CompL, {10}); + ecs_set(world, e_1, CompM, {10}); + ecs_set(world, e_1, CompN, {10}); + ecs_set(world, e_1, CompO, {10}); + ecs_set(world, e_1, CompP, {10}); + ecs_set(world, e_1, CompQ, {10}); + ecs_set(world, e_1, CompR, {10}); + ecs_set(world, e_1, CompS, {10}); + ecs_set(world, e_1, CompT, {10}); + + ecs_entity_t e_2 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_2, CompB, {10}); + ecs_set(world, e_2, CompC, {10}); + ecs_set(world, e_2, CompD, {10}); + ecs_set(world, e_2, CompE, {10}); + ecs_set(world, e_2, CompF, {10}); + ecs_set(world, e_2, CompG, {10}); + ecs_set(world, e_2, CompH, {10}); + ecs_set(world, e_2, CompI, {10}); + ecs_set(world, e_2, CompJ, {10}); + ecs_set(world, e_2, CompK, {10}); + ecs_set(world, e_2, CompL, {10}); + ecs_set(world, e_2, CompM, {10}); + ecs_set(world, e_2, CompN, {10}); + ecs_set(world, e_2, CompO, {10}); + ecs_set(world, e_2, CompP, {10}); + ecs_set(world, e_2, CompQ, {10}); + ecs_set(world, e_2, CompR, {10}); + ecs_set(world, e_2, CompS, {10}); + ecs_set(world, e_2, CompT, {10}); + ecs_set(world, e_2, CompU, {10}); + + ecs_filter_t f; + test_int(ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms_buffer = (ecs_term_t[]){ + {ecs_id(CompA)}, {ecs_id(CompB)}, {ecs_id(CompC)}, {ecs_id(CompD)}, + {ecs_id(CompE)}, {ecs_id(CompF)}, {ecs_id(CompG)}, {ecs_id(CompH)}, + {ecs_id(CompI)}, {ecs_id(CompJ)}, {ecs_id(CompK)}, {ecs_id(CompL)}, + {ecs_id(CompM)}, {ecs_id(CompN)}, {ecs_id(CompO)}, {ecs_id(CompP)}, + {ecs_id(CompQ)}, {ecs_id(CompR)}, {ecs_id(CompS)}, {ecs_id(CompT)} + }, + .terms_buffer_count = 20 + }), 0); + + ecs_iter_t it = ecs_filter_iter(world, &f); + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + test_int(ecs_term_id(&it, 11), ecs_id(CompK)); + test_int(ecs_term_id(&it, 12), ecs_id(CompL)); + test_int(ecs_term_id(&it, 13), ecs_id(CompM)); + test_int(ecs_term_id(&it, 14), ecs_id(CompN)); + test_int(ecs_term_id(&it, 15), ecs_id(CompO)); + test_int(ecs_term_id(&it, 16), ecs_id(CompP)); + test_int(ecs_term_id(&it, 17), ecs_id(CompQ)); + test_int(ecs_term_id(&it, 18), ecs_id(CompR)); + test_int(ecs_term_id(&it, 19), ecs_id(CompS)); + test_int(ecs_term_id(&it, 20), ecs_id(CompT)); + + int i; + for (i = 0; i < 20; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(ecs_filter_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + test_int(ecs_term_id(&it, 11), ecs_id(CompK)); + test_int(ecs_term_id(&it, 12), ecs_id(CompL)); + test_int(ecs_term_id(&it, 13), ecs_id(CompM)); + test_int(ecs_term_id(&it, 14), ecs_id(CompN)); + test_int(ecs_term_id(&it, 15), ecs_id(CompO)); + test_int(ecs_term_id(&it, 16), ecs_id(CompP)); + test_int(ecs_term_id(&it, 17), ecs_id(CompQ)); + test_int(ecs_term_id(&it, 18), ecs_id(CompR)); + test_int(ecs_term_id(&it, 19), ecs_id(CompS)); + test_int(ecs_term_id(&it, 20), ecs_id(CompT)); + + for (i = 0; i < 20; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(!ecs_filter_next(&it)); + + ecs_filter_fini(&f); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Get_component.c b/fggl/ecs2/flecs/test/api/src/Get_component.c new file mode 100644 index 0000000000000000000000000000000000000000..a963edd6e03434d4cb0c306e9e7f0f038afe94ef --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Get_component.c @@ -0,0 +1,215 @@ +#include <api.h> + +void Get_component_setup() { + ecs_tracing_enable(-3); +} + +void Get_component_get_empty() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert(ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 0) == 0); + + ecs_fini(world); +} + +void Get_component_get_1_from_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 0) == ecs_id(Position)); + + ecs_fini(world); +} + +void Get_component_get_1_from_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 0) == ecs_id(Position)); + + ecs_fini(world); +} + +void Get_component_get_2_from_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 1) == ecs_id(Velocity)); + + ecs_fini(world); +} + +void Get_component_get_2_from_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 1) == ecs_id(Velocity)); + + ecs_fini(world); +} + +static +void Test_main_stage(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + test_assert(*ecs_vector_get(ecs_get_type(it->world, e), ecs_entity_t, 0) == ecs_id(Position)); + } +} + +void Get_component_get_1_from_2_in_progress_from_main_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ECS_SYSTEM(world, Test_main_stage, EcsOnUpdate, Position); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +static +void Add_in_progress(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + ecs_type_t ecs_type(Velocity) = NULL; + + if (it->column_count >= 2) { + ecs_type(Velocity) = ecs_column_type(it, 2); + } + + for (int i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_add(it->world, e, Velocity); + } +} + +void Get_component_get_1_from_2_add_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ECS_SYSTEM(world, Add_in_progress, EcsOnUpdate, Position, :Velocity); + + ecs_progress(world, 1); + test_assert( ecs_has(world, e, Velocity)); + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 1) == ecs_id(Velocity)); + + ecs_fini(world); +} + +static +void Add_in_progress_test_main(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + test_assert(*ecs_vector_get(ecs_get_type(it->world, e), ecs_entity_t, 0) == ecs_id(Position)); + ecs_add(it->world, e, Velocity); + } +} + +void Get_component_get_both_from_2_add_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ECS_SYSTEM(world, Add_in_progress_test_main, EcsOnUpdate, Position, :Velocity); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Velocity)); + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 0) == ecs_id(Position)); + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 1) == ecs_id(Velocity)); + + ecs_fini(world); +} + +static +void Add_remove_in_progress_test_main(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + for (int i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + test_assert(*ecs_vector_get(ecs_get_type(it->world, e), ecs_entity_t, 0) == ecs_id(Position)); + ecs_add(it->world, e, Velocity); + ecs_remove(it->world, e, Position); + } +} + +void Get_component_get_both_from_2_add_remove_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ECS_SYSTEM(world, Add_remove_in_progress_test_main, EcsOnUpdate, Position, :Velocity); + + ecs_progress(world, 1); + + test_assert( !ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + test_assert(*ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 0) == ecs_id(Velocity)); + test_assert(ecs_vector_get(ecs_get_type(world, e), ecs_entity_t, 1) == 0); + + ecs_fini(world); +} + +void Get_component_get_childof_component() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const EcsComponent *ptr = ecs_get(world, ecs_id(Position), EcsComponent); + test_assert(ptr != NULL); + + test_expect_abort(); + ecs_get(world, ecs_pair(EcsChildOf, ecs_id(Position)), EcsComponent); +} diff --git a/fggl/ecs2/flecs/test/api/src/GlobalComponentIds.c b/fggl/ecs2/flecs/test/api/src/GlobalComponentIds.c new file mode 100644 index 0000000000000000000000000000000000000000..19fcb1f8462fce362b8c5b2dc57f21091f725602 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/GlobalComponentIds.c @@ -0,0 +1,135 @@ +#include <api.h> + +ECS_TAG_DECLARE(MyTag); +ECS_ENTITY_DECLARE(MyEntity); +ECS_COMPONENT_DECLARE(Position); +ECS_COMPONENT_DECLARE(Velocity); +ECS_TYPE_DECLARE(Movable); + +static +ecs_entity_t create_entity(ecs_world_t *world) { + return ecs_new(world, Position); +} + +static +ecs_entity_t create_entity_w_entity(ecs_world_t *world) { + return ecs_new_w_entity(world, ecs_id(Position)); +} + +static +ecs_entity_t create_tag_entity(ecs_world_t *world) { + return ecs_new(world, MyTag); +} + +static +ecs_entity_t create_tag_entity_w_entity(ecs_world_t *world) { + return ecs_new_w_entity(world, MyTag); +} + +static ecs_entity_t return_entity() { + return MyEntity; +} + +static +ecs_entity_t create_type_entity(ecs_world_t *world) { + return ecs_new(world, Movable); +} + +void GlobalComponentIds_declare() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + + ecs_entity_t e = create_entity(world); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void GlobalComponentIds_declare_w_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + + ecs_entity_t e = create_entity_w_entity(world); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void GlobalComponentIds_declare_2_world() { + ecs_world_t *world_1 = ecs_init(); + + ECS_COMPONENT_DEFINE(world_1, Position); + ECS_COMPONENT_DEFINE(world_1, Velocity); + + ecs_entity_t e_pos = ecs_id(Position); + ecs_entity_t e_vel = ecs_id(Velocity); + + ecs_world_t *world_2 = ecs_init(); + + /* Declare in reverse order, ensure that ids are still the same */ + ECS_COMPONENT_DEFINE(world_2, Velocity); + ECS_COMPONENT_DEFINE(world_2, Position); + + test_assert(e_pos == ecs_id(Position)); + test_assert(e_vel == ecs_id(Velocity)); + + ecs_fini(world_1); + ecs_fini(world_2); +} + +void GlobalComponentIds_declare_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG_DEFINE(world, MyTag); + + ecs_entity_t e = create_tag_entity(world); + test_assert(e != 0); + test_assert(ecs_has(world, e, MyTag)); + + ecs_fini(world); +} + +void GlobalComponentIds_declare_tag_w_entity() { + ecs_world_t *world = ecs_init(); + + ECS_TAG_DEFINE(world, MyTag); + + ecs_entity_t e = create_tag_entity_w_entity(world); + test_assert(e != 0); + test_assert(ecs_has(world, e, MyTag)); + + ecs_fini(world); +} + +void GlobalComponentIds_declare_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + ECS_ENTITY_DEFINE(world, MyEntity, Position); + + ecs_entity_t e = return_entity(); + test_assert(e != 0); + test_assert(e == MyEntity); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void GlobalComponentIds_declare_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + ECS_COMPONENT_DEFINE(world, Velocity); + ECS_TYPE_DEFINE(world, Movable, Position, Velocity); + + ecs_entity_t e = create_type_entity(world); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Has.c b/fggl/ecs2/flecs/test/api/src/Has.c new file mode 100644 index 0000000000000000000000000000000000000000..836688d210c4eb71ac99519f0e334606477d08c3 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Has.c @@ -0,0 +1,342 @@ +#include <api.h> +#include <flecs/type.h> + +void Has_zero() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert(ecs_has(world, e, 0)); + + ecs_fini(world); +} + +void Has_zero_from_nonzero() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(ecs_has(world, e, 0)); + + ecs_fini(world); +} + +void Has_1_of_0() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Has_2_of_0() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Type)); + + ecs_fini(world); +} + + +void Has_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Has_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Has_2_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_assert(ecs_has(world, e, Type)); + + ecs_fini(world); +} + +void Has_3_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position, Velocity, Mass); + + ecs_entity_t e = ecs_new(world, Type_1); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Type_2)); + + ecs_fini(world); +} + +void Has_2_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Type)); + + ecs_fini(world); +} + +void Has_1_of_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void TestHas(ecs_iter_t *it) { + ecs_type_t ecs_type(Position) = ecs_column_type(it, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_assert( ecs_has(it->world, it->entities[i], Position)); + } +} + +void Has_has_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, TestHas, EcsOnUpdate, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +void Has_has_of_zero() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ecs_has(world, 0, 0); +} + +void Has_owns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_owns(world, e, Position, true)); + + ecs_entity_t base = ecs_new(world, Velocity); + test_assert( ecs_has(world, base, Velocity)); + test_assert( ecs_owns(world, base, Velocity, true)); + + ecs_add_pair(world, e, EcsIsA, base); + test_assert( ecs_has(world, e, Velocity)); + test_assert( !ecs_owns(world, e, Velocity, true)); + + ecs_fini(world); +} + +void Has_has_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_add_pair(world, e, EcsIsA, base); + test_assert( !ecs_has_entity(world, e, base)); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + + ecs_fini(world); +} + +void Has_has_entity_0() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + test_expect_abort(); + + ecs_has_entity(world, 0, base); +} + +void Has_has_entity_0_component() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert( !ecs_has_entity(world, e, 0)); + + ecs_fini(world); +} + +void Has_has_entity_owned() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_entity_t f = ecs_new(world, 0); + test_assert(f != 0); + + ecs_entity_t g = ecs_new(world, 0); + test_assert(g != 0); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_add_id(world, e, f); + ecs_add_id(world, base, g); + ecs_add_pair(world, e, EcsIsA, base); + + test_assert( ecs_has_entity(world, e, f)); + test_assert( ecs_has_entity(world, e, g)); + test_assert( !ecs_has_entity(world, e, base)); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + test_assert( ecs_owns_entity(world, e, f, true)); + test_assert( !ecs_owns_entity(world, e, g, true)); + test_assert( !ecs_owns_entity(world, e, base, true)); + + test_assert( ecs_owns_entity(world, e, ecs_pair(EcsIsA, base), true)); + + ecs_fini(world); +} + +void Has_has_entity_owned_0() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_expect_abort(); + ecs_owns_entity(world, 0, e, true); +} + +void Has_has_entity_owned_0_component() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_assert( ecs_owns_entity(world, e, 0, true) == false); + + ecs_fini(world); +} + +void Has_has_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag_1); + ECS_TAG(world, Tag_2); + + ecs_entity_t e1 = ecs_new_id(world); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_new_id(world); + test_assert(e2 != 0); + + ecs_add_id(world, e1, Tag_1); + + test_bool(ecs_has_id(world, e1, Tag_1), true); + test_bool(ecs_has_id(world, e1, Tag_2), false); + test_bool(ecs_has_id(world, e1, EcsWildcard), true); + + test_bool(ecs_has_id(world, e2, Tag_1), false); + test_bool(ecs_has_id(world, e2, Tag_2), false); + test_bool(ecs_has_id(world, e2, EcsWildcard), false); + + ecs_fini(world); +} + +void Has_has_wildcard_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + ecs_add_pair(world, e, Rel, Obj_1); + + test_bool(ecs_has_pair(world, e, Rel, Obj_1), true); + test_bool(ecs_has_pair(world, e, Rel, Obj_2), false); + test_bool(ecs_has_pair(world, e, Rel, EcsWildcard), true); + test_bool(ecs_has_pair(world, e, EcsWildcard, Obj_1), true); + test_bool(ecs_has_pair(world, e, EcsWildcard, Obj_2), false); + test_bool(ecs_has_pair(world, e, EcsWildcard, EcsWildcard), true); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Hierarchies.c b/fggl/ecs2/flecs/test/api/src/Hierarchies.c new file mode 100644 index 0000000000000000000000000000000000000000..ca00faa8ef6b1f31ef51fd1febdfc42e7d207d32 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Hierarchies.c @@ -0,0 +1,1646 @@ +#include <api.h> + +void Hierarchies_setup() { + ecs_tracing_enable(-2); +} + +void Hierarchies_empty_scope() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_get_scope(world) == 0); + + ecs_fini(world); +} + +void Hierarchies_get_parent() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Child, 0); + + ecs_entity_t e = ecs_get_parent_w_entity(world, Child, 0); + test_assert(e == 0); + + ecs_fini(world); +} + +void Hierarchies_get_parent_from_nested() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, Child, (ChildOf, Scope)); + + ecs_entity_t e = ecs_get_parent_w_entity(world, Child, 0); + test_assert(e == Scope); + + ecs_fini(world); +} + +void Hierarchies_get_parent_from_nested_2() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, ChildScope, (ChildOf, Scope)); + ECS_ENTITY(world, Child, (ChildOf, Scope.ChildScope)); + + ecs_entity_t e = ecs_get_parent_w_entity(world, Child, 0); + test_assert(e == ChildScope); + + ecs_fini(world); +} + +void Hierarchies_get_parent_from_root() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, Child, (ChildOf, Scope)); + + ecs_entity_t e = ecs_get_parent_w_entity(world, 0, 0); + test_assert(e == 0); + + ecs_fini(world); +} + +void Hierarchies_delete_children() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child != 0); + + ecs_delete(world, parent); + + test_bool(ecs_exists(world, child), true); + test_bool(ecs_is_alive(world, child), false); + + ecs_fini(world); +} + +void Hierarchies_tree_iter_empty() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + ecs_iter_t it = ecs_scope_iter(world, Parent); + test_assert( !ecs_scope_next(&it)); + + ecs_fini(world); +} + +void Hierarchies_tree_iter_1_table() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + ECS_ENTITY(world, Child1, (ChildOf, Parent)); + ECS_ENTITY(world, Child2, (ChildOf, Parent)); + ECS_ENTITY(world, Child3, (ChildOf, Parent)); + + ecs_iter_t it = ecs_scope_iter(world, Parent); + test_assert( ecs_scope_next(&it) == true); + test_int( it.count, 3); + + test_assert(it.entities[0] == Child1); + test_assert(it.entities[1] == Child2); + test_assert(it.entities[2] == Child3); + + test_assert( !ecs_scope_next(&it)); + + ecs_fini(world); +} + +void Hierarchies_tree_iter_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Parent, 0); + + ECS_ENTITY(world, Child1, (ChildOf, Parent)); + ECS_ENTITY(world, Child2, (ChildOf, Parent)); + ECS_ENTITY(world, Child3, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child4, (ChildOf, Parent), Position); + + ecs_iter_t it = ecs_scope_iter(world, Parent); + test_assert( ecs_scope_next(&it) == true); + test_int( it.count, 2); + test_int(it.entities[0], Child1); + test_int(it.entities[1], Child2); + + test_assert( ecs_scope_next(&it) == true); + test_int( it.count, 2); + test_int(it.entities[0], Child3); + test_int(it.entities[1], Child4); + + test_assert( !ecs_scope_next(&it)); + + ecs_fini(world); +} + +void Hierarchies_tree_iter_w_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Parent, 0); + + ECS_ENTITY(world, Child1, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child2, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child3, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child4, (ChildOf, Parent), Velocity); + + ecs_iter_t it = ecs_scope_iter_w_filter(world, Parent, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert( ecs_scope_next(&it) == true); + test_int( it.count, 3); + + test_assert(it.entities[0] == Child1); + test_assert(it.entities[1] == Child2); + test_assert(it.entities[2] == Child3); + + test_assert( !ecs_scope_next(&it)); + + ecs_fini(world); +} + + +void Hierarchies_path_depth_0() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + char *path = ecs_get_fullpath(world, Parent); + test_str(path, "Parent"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_depth_1() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + char *path = ecs_get_fullpath(world, Child); + test_str(path, "Parent.Child"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_depth_2() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + char *path = ecs_get_fullpath(world, GrandChild); + test_str(path, "Parent.Child.GrandChild"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_rel_path_from_root() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + char *path = ecs_get_path(world, 0, Parent); + test_str(path, "Parent"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_rel_path_from_self() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + char *path = ecs_get_path(world, Parent, Parent); + test_str(path, ""); + free(path); + + ecs_fini(world); +} + +void Hierarchies_rel_path_depth_1() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + char *path = ecs_get_path(world, Parent, Child); + test_str(path, "Child"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_rel_path_depth_2() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + char *path = ecs_get_path(world, Parent, GrandChild); + test_str(path, "Child.GrandChild"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_rel_path_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Parent2, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + char *path = ecs_get_path(world, Parent2, Child); + test_str(path, "Parent.Child"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_custom_sep() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + char *path = ecs_get_path_w_sep(world, 0, Child, "::", NULL); + test_str(path, "Parent::Child"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_custom_prefix() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + char *path = ecs_get_path_w_sep(world, 0, Child, "::", "::"); + test_str(path, "::Parent::Child"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_prefix_rel_match() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + char *path = ecs_get_path_w_sep(world, Parent, GrandChild, "::", "::"); + test_str(path, "Child::GrandChild"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_prefix_rel_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Parent2, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + char *path = ecs_get_path_w_sep(world, Parent2, GrandChild, "::", "::"); + test_str(path, "::Parent::Child::GrandChild"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_path_w_number() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, 1000); + ecs_set_name(world, e, "Foo"); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "1000.Foo"); + free(path); + + ecs_fini(world); +} + + +void Hierarchies_lookup_depth_0() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Parent"); + test_assert(e == Parent); + + ecs_fini(world); +} + +void Hierarchies_lookup_depth_1() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Parent.Child"); + test_assert(e == Child); + + ecs_fini(world); +} + +void Hierarchies_lookup_depth_2() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Parent.Child.GrandChild"); + test_assert(e == GrandChild); + + ecs_fini(world); +} + +void Hierarchies_lookup_rel_0() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + ecs_entity_t e = ecs_lookup_path(world, Parent, "Child"); + test_assert(e == Child); + + ecs_fini(world); +} + +void Hierarchies_lookup_rel_1() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + ecs_entity_t e = ecs_lookup_path(world, Parent, "Child.GrandChild"); + test_assert(e == GrandChild); + + ecs_fini(world); +} + +void Hierarchies_lookup_rel_2() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + ECS_ENTITY(world, GrandGrandChild, (ChildOf, Parent.Child.GrandChild)); + + ecs_entity_t e = ecs_lookup_path(world, Parent, "Child.GrandChild.GrandGrandChild"); + test_assert(e == GrandGrandChild); + + ecs_fini(world); +} + +void Hierarchies_lookup_custom_sep() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + ecs_entity_t e = ecs_lookup_path_w_sep(world, 0, "Parent::Child", "::", NULL, true); + test_assert(e == Child); + + ecs_fini(world); +} + +void Hierarchies_lookup_custom_prefix() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + ecs_entity_t e = ecs_lookup_path_w_sep(world, 0, "::Parent::Child", "::", "::", true); + test_assert(e == Child); + + ecs_fini(world); +} + +void Hierarchies_lookup_custom_prefix_from_root() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + ecs_entity_t e = ecs_lookup_path_w_sep(world, Parent, "::Parent::Child", "::", "::", true); + test_assert(e == Child); + + ecs_fini(world); +} + +void Hierarchies_lookup_self() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + ecs_entity_t e = ecs_lookup_path(world, Parent, ""); + test_assert(e == Parent); + + ecs_fini(world); +} + +void Hierarchies_lookup_number() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Parent2, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + ECS_ENTITY(world, GrandChild, (ChildOf, Parent.Child)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, 1000); + ecs_set_name(world, e, "Foo"); + + ecs_entity_t c = ecs_lookup_path(world, 0, "1000.Foo"); + test_assert(e == c); + + ecs_fini(world); +} + +void Hierarchies_scope_set() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + ecs_entity_t scope = ecs_get_scope(world); + test_assert(scope == Scope); + + ecs_fini(world); +} + +void Hierarchies_scope_set_w_new() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, EcsChildOf, Scope)); + + old_scope = ecs_set_scope(world, 0); + test_assert(old_scope == Scope); + + e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void Hierarchies_scope_set_w_new_staged() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + + ecs_defer_begin(world); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + old_scope = ecs_set_scope(world, 0); + test_assert(old_scope == Scope); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + + ecs_defer_end(world); + + test_assert(ecs_has_pair(world, e1, EcsChildOf, Scope)); + test_assert(ecs_get_type(world, e2) == NULL); + + ecs_fini(world); +} + +void Hierarchies_scope_set_again() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, ChildScope, 0); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + old_scope = ecs_set_scope(world, ChildScope); + test_assert(old_scope == Scope); + + ecs_fini(world); +} + +void Hierarchies_scope_set_w_lookup() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, Child, (ChildOf, Scope)); + + test_assert( ecs_lookup_fullpath(world, "Child") == 0); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Child"); + test_assert(e != 0); + test_assert(e == Child); + + old_scope = ecs_set_scope(world, 0); + test_assert(old_scope == Scope); + + test_assert( ecs_lookup_fullpath(world, "Child") == 0); + + ecs_fini(world); +} + +void Hierarchies_lookup_in_parent_from_scope() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, ChildScope, (ChildOf, Scope)); + ECS_ENTITY(world, Child, (ChildOf, Scope)); + + test_assert( ecs_lookup_fullpath(world, "Child") == 0); + + ecs_entity_t old_scope = ecs_set_scope(world, ChildScope); + test_assert(old_scope == 0); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Child"); + test_assert(e != 0); + test_assert(e == Child); + + old_scope = ecs_set_scope(world, 0); + test_assert(old_scope == ChildScope); + + test_assert( ecs_lookup_fullpath(world, "Child") == 0); + + ecs_fini(world); +} + +void Hierarchies_lookup_in_root_from_scope() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + ECS_ENTITY(world, ChildScope, (ChildOf, Scope)); + ECS_ENTITY(world, Child, 0); + + ecs_entity_t old_scope = ecs_set_scope(world, ChildScope); + test_assert(old_scope == 0); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Child"); + test_assert(e != 0); + test_assert(e == Child); + + old_scope = ecs_set_scope(world, 0); + test_assert(old_scope == ChildScope); + + ecs_fini(world); +} + +void Hierarchies_scope_component() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Scope, 0); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_lookup_fullpath(world, "Position"); + test_assert(e != 0); + test_assert(e == ecs_id(Position)); + + old_scope = ecs_set_scope(world, 0); + test_assert(old_scope == Scope); + + e = ecs_lookup_fullpath(world, "Position"); + test_assert(e == 0); + + e = ecs_lookup_fullpath(world, "Scope.Position"); + test_assert(e == ecs_id(Position)); + + ecs_fini(world); +} + +void Hierarchies_scope_component_no_macro() { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Scope = ecs_new_id(world); + + ecs_entity_t old_scope = ecs_set_scope(world, Scope); + test_assert(old_scope == 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t) { + .use_low_id = true + }); + + test_assert(e2 != 0); + test_assert(ecs_has_pair(world, e2, EcsChildOf, Scope)); + + + ecs_fini(world); + + /* The real purpose of this test is to catch an issue where a component was + * added to multiple tables, which caused a crash during ecs_fini */ +} + +void Hierarchies_fullpath_for_core() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, (ChildOf, Parent)); + + char *path = ecs_get_fullpath(world, ecs_id(EcsComponent)); + test_str(path, "Component"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_new_from_path_depth_0() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 1); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "foo"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_new_from_path_depth_1() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "bar"); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 2); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "foo.bar"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_new_from_path_depth_2() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar.hello"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "hello"); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 2); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "foo.bar.hello"); + free(path); + + ecs_fini(world); +} + + +void Hierarchies_new_from_path_existing_depth_0() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t f = ecs_new_from_path(world, 0, "foo"); + test_assert(f != 0); + test_assert(f == e); + test_str(ecs_get_name(world, e), "foo"); + + ecs_fini(world); +} + +void Hierarchies_new_from_path_existing_depth_1() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "bar"); + + ecs_entity_t f = ecs_new_from_path(world, 0, "foo.bar"); + test_assert(f != 0); + test_assert(f == e); + test_str(ecs_get_name(world, e), "bar"); + + ecs_fini(world); +} + +void Hierarchies_new_from_path_existing_depth_2() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar.hello"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "hello"); + + ecs_entity_t f = ecs_new_from_path(world, 0, "foo.bar.hello"); + test_assert(f != 0); + test_assert(f == e); + test_str(ecs_get_name(world, e), "hello"); + + ecs_fini(world); +} + +void Hierarchies_add_path_depth_0() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t id = ecs_new(world, 0); + test_assert(id != 0); + + ecs_entity_t e = ecs_add_path(world, id, 0, "foo"); + test_assert(e != 0); + test_assert(e == id); + test_str(ecs_get_name(world, e), "foo"); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 1); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "foo"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_add_path_depth_1() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t id = ecs_new(world, 0); + test_assert(id != 0); + + ecs_entity_t e = ecs_add_path(world, id, 0, "foo.bar"); + test_assert(e != 0); + test_assert(e == id); + test_str(ecs_get_name(world, e), "bar"); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 2); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "foo.bar"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_add_path_depth_2() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t id = ecs_new(world, 0); + test_assert(id != 0); + + ecs_entity_t e = ecs_add_path(world, id, 0, "foo.bar.hello"); + test_assert(e != 0); + test_assert(e == id); + test_str(ecs_get_name(world, e), "hello"); + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 2); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "foo.bar.hello"); + free(path); + + ecs_fini(world); +} + +void Hierarchies_add_path_existing_depth_0() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "foo"); + + ecs_entity_t id = ecs_new(world, 0); + test_assert(id != 0); + + ecs_entity_t f = ecs_add_path(world, id, 0, "foo"); + test_assert(f != 0); + test_assert(f != id); + test_assert(f == e); + test_str(ecs_get_name(world, e), "foo"); + + ecs_fini(world); +} + +void Hierarchies_add_path_existing_depth_1() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "bar"); + + ecs_entity_t id = ecs_new(world, 0); + test_assert(id != 0); + + ecs_entity_t f = ecs_add_path(world, id, 0, "foo.bar"); + test_assert(f != 0); + test_assert(f != id); + test_assert(f == e); + test_str(ecs_get_name(world, e), "bar"); + + ecs_fini(world); +} + +void Hierarchies_add_path_existing_depth_2() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar.hello"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "hello"); + + ecs_entity_t id = ecs_new(world, 0); + test_assert(id != 0); + + ecs_entity_t f = ecs_add_path(world, id, 0, "foo.bar.hello"); + test_assert(f != 0); + test_assert(f != id); + test_assert(f == e); + test_str(ecs_get_name(world, e), "hello"); + + ecs_fini(world); +} + +void Hierarchies_add_path_from_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "bar"); + + ecs_set_scope(world, e); + + ecs_entity_t id = ecs_new_id(world); + test_assert(id != 0); + + ecs_entity_t f = ecs_add_path(world, id, 0, "hello.world"); + test_assert(f != 0); + test_str(ecs_get_name(world, f), "world"); + + char *path = ecs_get_fullpath(world, f); + test_str(path, "hello.world"); + ecs_os_free(path); + + ecs_set_scope(world, 0); + + ecs_fini(world); +} + +void Hierarchies_add_path_from_scope_new_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_from_path(world, 0, "foo.bar"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "bar"); + + ecs_set_scope(world, e); + + ecs_entity_t id = ecs_add_path(world, 0, 0, "hello.world"); + test_assert(id != 0); + test_str(ecs_get_name(world, id), "world"); + + char *path = ecs_get_fullpath(world, id); + test_str(path, "foo.bar.hello.world"); + ecs_os_free(path); + + ecs_set_scope(world, 0); + + ecs_fini(world); +} + +void Hierarchies_new_w_child_in_root() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_new(world, 0); + ecs_entity_t parent = ecs_new(world, 0); + + ecs_set_scope(world, scope); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert( ecs_has_pair(world, child, EcsChildOf, parent)); + test_assert( !ecs_has_pair(world, child, EcsChildOf, scope)); + + ecs_entity_t child_2 = ecs_new(world, 0); + test_assert( !ecs_has_pair(world, child_2, EcsChildOf, parent)); + test_assert( ecs_has_pair(world, child_2, EcsChildOf, scope)); + + ecs_fini(world); +} + +void Hierarchies_delete_child() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(ecs_get_type(world, child) != NULL); + + ecs_delete(world, parent); + + test_bool(ecs_exists(world, parent), true); + test_bool(ecs_exists(world, child), true); + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child), false); + + ecs_fini(world); +} + +void Hierarchies_delete_2_children() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child_1 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_1 != 0); + test_assert(ecs_get_type(world, child_1) != NULL); + + ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_2 != 0); + test_assert(ecs_get_type(world, child_2) != NULL); + + ecs_delete(world, parent); + + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child_1), false); + test_bool(ecs_is_alive(world, child_2), false); + + ecs_fini(world); +} + +void Hierarchies_delete_2_children_different_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child_1 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_1 != 0); + test_assert(ecs_get_type(world, child_1) != NULL); + ecs_add(world, child_1, Position); + + ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_2 != 0); + test_assert(ecs_get_type(world, child_2) != NULL); + ecs_add(world, child_2, Velocity); + + ecs_delete(world, parent); + + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child_1), false); + test_bool(ecs_is_alive(world, child_2), false); + + ecs_fini(world); +} + +void Hierarchies_delete_tree_2_levels() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(ecs_get_type(world, child) != NULL); + + ecs_entity_t grand_child = ecs_new_w_pair(world, EcsChildOf, child); + test_assert(ecs_get_type(world, grand_child) != NULL); + + ecs_delete(world, parent); + + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child), false); + test_bool(ecs_is_alive(world, grand_child), false); + + ecs_fini(world); +} + +void Hierarchies_delete_tree_3_levels() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(ecs_get_type(world, child) != NULL); + + ecs_entity_t grand_child = ecs_new_w_pair(world, EcsChildOf, child); + test_assert(ecs_get_type(world, grand_child) != NULL); + + ecs_entity_t great_grand_child = ecs_new_w_pair(world, EcsChildOf, grand_child); + test_assert(ecs_get_type(world, great_grand_child) != NULL); + + ecs_delete(world, parent); + + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child), false); + test_bool(ecs_is_alive(world, grand_child), false); + test_bool(ecs_is_alive(world, great_grand_child), false); + + ecs_fini(world); +} + +void Hierarchies_delete_tree_count_tables() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, Position); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(ecs_get_type(world, child) != NULL); + ecs_add(world, child, Position); + + ecs_entity_t grand_child = ecs_new_w_pair(world, EcsChildOf, child); + test_assert(ecs_get_type(world, grand_child) != NULL); + ecs_add(world, grand_child, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + test_int(it.table_count, 3); + test_int(it.inactive_table_count, 0); + + ecs_delete(world, parent); + + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child), false); + test_bool(ecs_is_alive(world, grand_child), false); + + it = ecs_query_iter(q); + test_int(it.table_count, 0); + test_int(it.inactive_table_count, 1); /* Parent table is still there */ + + ecs_fini(world); +} + +void Hierarchies_delete_tree_staged() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, Position); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(ecs_get_type(world, child) != NULL); + ecs_add(world, child, Position); + + ecs_entity_t grand_child = ecs_new_w_pair(world, EcsChildOf, child); + test_assert(ecs_get_type(world, grand_child) != NULL); + ecs_add(world, grand_child, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_iter_t it = ecs_query_iter(q); + test_int(it.table_count, 3); + test_int(it.inactive_table_count, 0); + + ecs_defer_begin(world); + ecs_delete(world, parent); + ecs_defer_end(world); + + test_bool(ecs_is_alive(world, parent), false); + test_bool(ecs_is_alive(world, child), false); + test_bool(ecs_is_alive(world, grand_child), false); + + it = ecs_query_iter(q); + test_int(it.table_count, 0); + test_int(it.inactive_table_count, 1); /* Parent table is still there */ + + ecs_fini(world); +} + +void Hierarchies_delete_tree_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child, Position, (ChildOf, Parent)); + + ecs_delete(world, Parent); + + test_assert(!ecs_is_alive(world, Parent)); + test_assert(!ecs_is_alive(world, Child)); + + ecs_fini(world); +} + +void Hierarchies_delete_tree_recreate() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(parent != 0); + test_assert(child != 0); + test_assert(ecs_has_pair(world, child, EcsChildOf, parent)); + + ecs_delete_children(world, parent); + + ecs_new(world, Position); + + ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_2 != 0); + test_assert(ecs_has_pair(world, child_2, EcsChildOf, parent)); + + ecs_fini(world); +} + +void Hierarchies_get_child_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child1, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child2, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child3, (ChildOf, Parent), Position); + + test_int(ecs_get_child_count(world, Parent), 3); + + ecs_fini(world); +} + +void Hierarchies_get_child_count_no_children() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + test_int(ecs_get_child_count(world, Parent), 0); + + ecs_fini(world); +} + +void Hierarchies_get_child_count_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, Child1, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child2, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child3, (ChildOf, Parent), Position); + ECS_ENTITY(world, Child4, (ChildOf, Parent), Velocity); + + test_int(ecs_get_child_count(world, Parent), 4); + + ecs_fini(world); +} + +void Hierarchies_scope_iter_after_delete_tree() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(parent != 0); + test_assert(child != 0); + test_assert(ecs_has_pair(world, child, EcsChildOf, parent)); + + ecs_delete_children(world, parent); + + ecs_iter_t it = ecs_scope_iter(world, parent); + test_assert(!ecs_scope_next(&it)); + + ecs_fini(world); +} + +void Hierarchies_add_child_after_delete_tree() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(parent != 0); + test_assert(child != 0); + test_assert(ecs_has_pair(world, child, EcsChildOf, parent)); + + ecs_delete_children(world, parent); + + child = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_fini(world); +} + +static int on_remove_count = 0; + +static +void RemovePosition(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + on_remove_count ++; + } +} + +void Hierarchies_delete_tree_w_onremove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, RemovePosition, EcsOnRemove, Position); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + ecs_entity_t child_1 = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_1 != 0); + test_assert(child_2 != 0); + test_assert(ecs_has_pair(world, child_1, EcsChildOf, parent)); + test_assert(ecs_has_pair(world, child_2, EcsChildOf, parent)); + + ecs_add(world, parent, Position); + ecs_add(world, child_1, Position); + ecs_add(world, child_2, Position); + + ecs_delete(world, parent); + + test_assert( !ecs_is_alive(world, parent)); + test_assert( !ecs_is_alive(world, child_1)); + test_assert( !ecs_is_alive(world, child_2)); + + test_int(on_remove_count, 3); + + ecs_fini(world); +} + +static int dtor_count = 0; + +static ECS_DTOR(Position, ptr, { + dtor_count ++; +}) + +void Hierarchies_delete_tree_w_dtor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set_component_actions(world, Position, { + .dtor = ecs_dtor(Position) + }); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + ecs_entity_t child_1 = ecs_new_w_pair(world, EcsChildOf, parent); + ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child_1 != 0); + test_assert(child_2 != 0); + test_assert(ecs_has_pair(world, child_1, EcsChildOf, parent)); + test_assert(ecs_has_pair(world, child_2, EcsChildOf, parent)); + + ecs_add(world, parent, Position); + ecs_add(world, child_1, Position); + ecs_add(world, child_2, Position); + + ecs_delete(world, parent); + + test_assert( !ecs_is_alive(world, parent)); + test_assert( !ecs_is_alive(world, child_1)); + test_assert( !ecs_is_alive(world, child_2)); + + test_int(dtor_count, 3); + + ecs_fini(world); +} + +void Hierarchies_add_child_to_recycled_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(child != 0); + test_assert(ecs_has_pair(world, child, EcsChildOf, parent)); + + ecs_delete(world, parent); + test_assert( !ecs_is_alive(world, parent)); + test_assert( !ecs_is_alive(world, child)); + + ecs_entity_t new_parent = ecs_new(world, 0); + test_assert(new_parent != 0); + /* Make sure we have a recycled identifier */ + test_assert(ecs_entity_t_lo(new_parent) != new_parent); + + child = ecs_new_w_pair(world, EcsChildOf, new_parent); + test_assert(child != 0); + test_assert(ecs_has_pair(world, child, EcsChildOf, new_parent)); + test_assert( ecs_is_alive(world, child)); + test_assert( ecs_is_alive(world, child)); + + ecs_fini(world); +} + +void Hierarchies_get_type_after_recycled_parent_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + test_assert( ecs_get_type(world, parent) == NULL); + + ecs_delete(world, parent); + test_assert( !ecs_is_alive(world, parent)); + + parent = ecs_new(world, Position); + test_assert(parent != 0); + test_assert(ecs_entity_t_lo(parent) != parent); // Ensure recycled + test_assert( ecs_get_type(world, parent) != NULL); + + ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(e != 0); + test_assert( ecs_get_type(world, parent) != NULL); + + ecs_fini(world); +} + +void Hierarchies_rematch_after_add_to_recycled_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_new(world, "Tag, PARENT:Position"); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_delete(world, parent); + test_assert( !ecs_is_alive(world, parent)); + + parent = ecs_new(world, 0); + test_assert(parent != 0); + + ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsChildOf, parent)); + ecs_add(world, e, Tag); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), false); + + ecs_set(world, parent, Position, {10, 20}); + + ecs_progress(world, 0); + + it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + + const Position *p = ecs_term(&it, Position, 2); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + test_assert(ecs_term_source(&it, 2) == parent); + + ecs_fini(world); +} + +void Hierarchies_cascade_after_recycled_parent_change() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_new(world, "Tag, CASCADE:Position"); + + ecs_entity_t parent = ecs_new(world, 0); + test_assert(parent != 0); + ecs_entity_t child = ecs_new(world, 0); + test_assert(child != 0); + + ecs_delete(world, parent); + test_assert( !ecs_is_alive(world, parent)); + ecs_delete(world, child); + test_assert( !ecs_is_alive(world, child)); + + parent = ecs_new_w_entity(world, Tag); + test_assert(parent != 0); + child = ecs_new_w_entity(world, Tag); + test_assert(child != 0); + ecs_add_pair(world, child, EcsChildOf, parent); + + ecs_entity_t e = ecs_new_w_entity(world, Tag); + test_assert(e != 0); + + ecs_add_pair(world, e, EcsChildOf, child); + test_assert( ecs_has_pair(world, e, EcsChildOf, child)); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_assert(it.entities[0] == parent); + test_assert(ecs_term_source(&it, 2) == 0); + const Position *p = ecs_term(&it, Position, 2); + test_assert(p == NULL); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_assert(it.entities[0] == child); + test_assert(ecs_term_source(&it, 2) == 0); + p = ecs_term(&it, Position, 2); + test_assert(p == NULL); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_assert(it.entities[0] == e); + test_assert(ecs_term_source(&it, 2) == 0); + p = ecs_term(&it, Position, 2); + test_assert(p == NULL); + + test_bool(ecs_query_next(&it), false); + + ecs_set(world, parent, Position, {10, 20}); + ecs_set(world, child, Position, {20, 30}); + + ecs_progress(world, 0); + + it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_assert(it.entities[0] == parent); + test_assert(ecs_term_source(&it, 2) == 0); + p = ecs_term(&it, Position, 2); + test_assert(p == NULL); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_assert(it.entities[0] == child); + test_assert(ecs_term_source(&it, 2) == parent); + p = ecs_term(&it, Position, 2); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_assert(it.entities[0] == e); + test_assert(ecs_term_source(&it, 2) == child); + p = ecs_term(&it, Position, 2); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 30); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Hierarchies_long_name_depth_0() { + ecs_world_t *world = ecs_init(); + + const char *parent_name = "a_parent_entity_with_an_identifier_longer_than_sixty_four_characters"; + test_assert(strlen(parent_name) >= 64); + + ecs_entity_t parent = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = parent_name + }); + + test_str(ecs_get_name(world, parent), parent_name); + + ecs_entity_t e = ecs_lookup_fullpath(world, parent_name); + test_assert(e == parent); + + char *path = ecs_get_fullpath(world, e); + test_str(path, parent_name); + ecs_os_free(path); + + ecs_fini(world); +} + +void Hierarchies_long_name_depth_1() { + ecs_world_t *world = ecs_init(); + + const char *parent_name = + "a_parent_entity_with_an_identifier_longer_than_sixty_four_characters"; + test_assert(strlen(parent_name) >= 64); + ecs_entity_t parent = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = parent_name + }); + test_str(ecs_get_name(world, parent), parent_name); + + const char *child_name = + "a_child_entity_with_an_identifier_longer_than_sixty_four_characters"; + test_assert(strlen(child_name) >= 64); + ecs_entity_t child = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = child_name, + .add = { ecs_pair(EcsChildOf, parent) } + }); + test_str(ecs_get_name(world, child), child_name); + + const char *search_path = + "a_parent_entity_with_an_identifier_longer_than_sixty_four_characters." + "a_child_entity_with_an_identifier_longer_than_sixty_four_characters"; + + ecs_entity_t e = ecs_lookup_fullpath(world, search_path); + test_assert(e == child); + + char *path = ecs_get_fullpath(world, e); + test_str(path, search_path); + ecs_os_free(path); + + ecs_fini(world); +} + +void Hierarchies_long_name_depth_2() { + ecs_world_t *world = ecs_init(); + + const char *parent_name = + "a_parent_entity_with_an_identifier_longer_than_sixty_four_characters"; + test_assert(strlen(parent_name) >= 64); + ecs_entity_t parent = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = parent_name + }); + test_str(ecs_get_name(world, parent), parent_name); + + const char *child_name = + "a_child_entity_with_an_identifier_longer_than_sixty_four_characters"; + test_assert(strlen(child_name) >= 64); + ecs_entity_t child = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = child_name, + .add = { ecs_pair(EcsChildOf, parent) } + }); + test_str(ecs_get_name(world, child), child_name); + + const char *grand_child_name = + "a_grand_child_entity_with_an_identifier_longer_than_sixty_four_characters"; + test_assert(strlen(grand_child_name) >= 64); + ecs_entity_t grand_child = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = grand_child_name, + .add = { ecs_pair(EcsChildOf, child) } + }); + test_str(ecs_get_name(world, grand_child), grand_child_name); + + const char *search_path = + "a_parent_entity_with_an_identifier_longer_than_sixty_four_characters." + "a_child_entity_with_an_identifier_longer_than_sixty_four_characters." + "a_grand_child_entity_with_an_identifier_longer_than_sixty_four_characters"; + + ecs_entity_t e = ecs_lookup_fullpath(world, search_path); + test_assert(e == grand_child); + + char *path = ecs_get_fullpath(world, e); + test_str(path, search_path); + ecs_os_free(path); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Internals.c b/fggl/ecs2/flecs/test/api/src/Internals.c new file mode 100644 index 0000000000000000000000000000000000000000..2791f8c4907be80a0b7c57a12f90fbdf671d1118 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Internals.c @@ -0,0 +1,223 @@ +#include <api.h> + +void Internals_setup() { + ecs_tracing_enable(-3); +} + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + ECS_COLUMN(it, Mass, m, 3); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + + if (v) { + v[i].x = 30; + v[i].y = 40; + } + + if (m) { + m[i] = 50; + } + } +} + +void Internals_deactivate_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + /* System is now matched with archetype of entities. Delete entities to + * deactivate table for system */ + ecs_delete(world, e1); + ecs_delete(world, e2); + + test_assert(true); + + ecs_fini(world); +} + +void Internals_activate_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + /* Add entities after system definition to trigger table activation */ + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + test_assert(true); + + ecs_fini(world); +} + +void Internals_activate_deactivate_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + /* Add entities after system definition to trigger table activation */ + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + /* System is now matched with archetype of entities. Delete entities to + * deactivate table for system */ + ecs_delete(world, e1); + ecs_delete(world, e2); + + test_assert(true); + + ecs_fini(world); +} + +void Internals_activate_deactivate_reactive() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + /* Add entities after system definition to trigger table activation */ + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + /* System is now matched with archetype of entities. Delete entities to + * deactivate table for system */ + ecs_delete(world, e1); + ecs_delete(world, e2); + + /* Add entities of same type to trigger table reactivation */ + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + test_assert(true); + + ecs_fini(world); +} + +void Internals_activate_deactivate_activate_other() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + /* Add entities after system definition to trigger table activation */ + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + /* System is now matched with archetype of entities. Delete entities to + * deactivate table for system */ + ecs_delete(world, e1); + ecs_delete(world, e2); + + /* Add entities of different type type to trigger new table activation */ + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity); + + test_assert(true); + + ecs_fini(world); +} + +static int invoked = 0; + +static +void CreateNewTable(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +static +void ManualSystem(ecs_iter_t *it) { + invoked ++; +} + +void Internals_no_double_system_table_after_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, CreateNewTable, EcsOnUpdate, Position, :Velocity); + ECS_SYSTEM(world, ManualSystem, 0, Position, Velocity); + + /* CreateNewTable system created a new, non-empty table. This will be merged + * which will trigger activation of ManualSystem. This will cause the system + * to go from the inactive_systems array to the manual_systems array. This + * happens as systems are notified of new tables (during the merge). Because + * the manual_systems array was evaluated after the inactive_systems array, + * a table could be added to a system twice. */ + ecs_progress(world, 0); + + /* Validate that the CreateNewTable system has ran */ + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + /* Now run the ManualSystem, and make sure it is only invoked once. If it is + * invoked twice, the table has been registered with the system twice, which + * is wrong. */ + ecs_run(world, ManualSystem, 0, NULL); + + test_int(invoked, 1); + + ecs_fini(world); +} + +void Internals_recreate_deleted_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t parent_A = ecs_new(world, 0); + ecs_entity_t child_A = ecs_new_w_pair(world, EcsChildOf, parent_A); + test_assert(parent_A != 0); + test_assert(child_A != 0); + + ecs_delete(world, parent_A); // Deletes table + test_assert( !ecs_is_alive(world, parent_A)); + test_assert( !ecs_is_alive(world, child_A)); + + ecs_entity_t parent_B = ecs_new(world, 0); + ecs_entity_t child_B = ecs_new_w_pair(world, EcsChildOf, parent_B); + test_assert(parent_B != 0); + test_assert(child_B != 0); + + ecs_fini(world); +} + +void Internals_create_65k_tables() { + ecs_world_t *world = ecs_init(); + + int32_t i; + for (i = 0; i <= 65536; i ++) { + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, e); + test_assert(ecs_has_entity(world, e, e)); + test_int(ecs_vector_count(ecs_get_type(world, e)), 1); + } + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Lookup.c b/fggl/ecs2/flecs/test/api/src/Lookup.c new file mode 100644 index 0000000000000000000000000000000000000000..82d2098a06b78ebe6b515305e3330446cefd9f76 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Lookup.c @@ -0,0 +1,322 @@ +#include <api.h> + +void Lookup_setup() { + ecs_tracing_enable(-3); +} + +void Lookup_lookup() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, MyEntity, 0); + + ecs_entity_t lookup = ecs_lookup(world, "MyEntity"); + test_assert(lookup != 0); + test_assert(lookup == MyEntity); + + ecs_fini(world); +} + +void Lookup_lookup_w_null_name() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, MyEntity, 0); + + /* Ensure this doesn't crash the lookup function */ + ecs_set_name(world, 0, NULL); + + ecs_entity_t lookup = ecs_lookup(world, "MyEntity"); + test_assert(lookup != 0); + test_assert(lookup == MyEntity); + + ecs_fini(world); +} + +void Lookup_lookup_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t lookup = ecs_lookup(world, "Position"); + test_assert(lookup != 0); + test_assert(lookup == ecs_id(Position)); + + ecs_fini(world); +} + +void Lookup_lookup_not_found() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t lookup = ecs_lookup(world, "foo"); + test_assert(lookup == 0); + + ecs_fini(world); +} + +void Lookup_lookup_child() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent1, 0); + ECS_ENTITY(world, Parent2, 0); + + ecs_entity_t e1 = ecs_set_name(world, 0, "Child"); + ecs_entity_t e2 = ecs_set_name(world, 0, "Child"); + + ecs_add_pair(world, e1, EcsChildOf, Parent1); + ecs_add_pair(world, e2, EcsChildOf, Parent2); + + ecs_entity_t lookup = ecs_lookup_child(world, Parent1, "Child"); + test_assert(lookup != 0); + test_assert(lookup == e1); + + lookup = ecs_lookup_child(world, Parent2, "Child"); + test_assert(lookup != 0); + test_assert(lookup == e2); + + ecs_fini(world); +} + +void Lookup_get_name() { + ecs_world_t *world = ecs_init(); + + /* Ensure this doesn't crash the lookup function */ + ecs_entity_t e = ecs_set_name(world, 0, "Entity"); + const char *id = ecs_get_name(world, e); + test_assert(id != NULL); + test_str(id, "Entity"); + + ecs_fini(world); +} + +void Lookup_get_name_no_name() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + /* Ensure this doesn't crash the lookup function */ + ecs_entity_t e = ecs_new(world, Position); + const char *id = ecs_get_name(world, e); + test_assert(id == NULL); + + ecs_fini(world); +} + +void Lookup_get_name_from_empty() { + ecs_world_t *world = ecs_init(); + + /* Ensure this doesn't crash the lookup function */ + ecs_entity_t e = ecs_new(world, 0); + const char *id = ecs_get_name(world, e); + test_assert(id == NULL); + + ecs_fini(world); +} + +void Lookup_lookup_by_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_lookup(world, "1000"); + test_int(e, 1000); + + ecs_fini(world); +} + +void Lookup_lookup_name_w_digit() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_set_name(world, 0, "10_id"); + ecs_entity_t e2 = ecs_lookup(world, "10_id"); + test_assert(e == e2); + + ecs_fini(world); +} + +void Lookup_lookup_symbol_by_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_lookup_symbol(world, "1000", true); + test_int(e, 1000); + + ecs_fini(world); +} + +void Lookup_lookup_symbol_w_digit() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_set_symbol(world, 0, "10_id"); + ecs_entity_t e2 = ecs_lookup_symbol(world, "10_id", true); + test_assert(e == e2); + + ecs_fini(world); +} + +void Lookup_lookup_path_w_digit() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t parent = ecs_set_name(world, 0, "parent"); + ecs_entity_t e1 = ecs_set_name(world, 0, "10_id"); + ecs_add_pair(world, e1, EcsChildOf, parent); + + ecs_entity_t e2 = ecs_lookup_fullpath(world, "parent.10_id"); + test_assert(e2 == e1); + + ecs_fini(world); +} + +void Lookup_set_name_of_existing() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(ecs_get_name(world, e) == NULL); + + ecs_set_name(world, e, "Foo"); + test_assert(ecs_get_name(world, e) != NULL); + test_str(ecs_get_name(world, e), "Foo"); + + ecs_fini(world); +} + +void Lookup_change_name_of_existing() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_set_name(world, 0, "Foo"); + test_assert(e != 0); + test_assert(ecs_get_name(world, e) != NULL); + test_str(ecs_get_name(world, e), "Foo"); + + ecs_set_name(world, e, "Bar"); + test_assert(ecs_get_name(world, e) != NULL); + test_str(ecs_get_name(world, e), "Bar"); + + ecs_fini(world); +} + +void Lookup_lookup_alias() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_set_name(world, 0, "MyEntity"); + test_assert(e != 0); + + ecs_use(world, e, "MyAlias"); + + ecs_entity_t a = ecs_lookup(world, "MyAlias"); + test_assert(a != 0); + test_assert(a == e); + + ecs_fini(world); +} + +void Lookup_lookup_scoped_alias() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t p = ecs_set_name(world, 0, "MyParent"); + test_assert(p != 0); + ecs_entity_t e = ecs_set_name(world, 0, "MyEntity"); + test_assert(e != 0); + ecs_add_pair(world, e, EcsChildOf, p); + + ecs_use(world, e, "MyAlias"); + + ecs_entity_t a = ecs_lookup(world, "MyAlias"); + test_assert(a != 0); + test_assert(a == e); + + ecs_fini(world); +} + +void Lookup_define_duplicate_alias() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_set_name(world, 0, "MyEntityA"); + test_assert(e1 != 0); + ecs_entity_t e2 = ecs_set_name(world, 0, "MyEntityB"); + test_assert(e2 != 0); + + test_expect_abort(); /* Not allowed to create duplicate aliases */ + + ecs_use(world, e1, "MyAlias"); + ecs_use(world, e2, "MyAlias"); + +} + +void Lookup_lookup_null() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_lookup(world, NULL) == 0); + + ecs_fini(world); +} + +void Lookup_lookup_symbol_null() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_lookup_symbol(world, NULL, true) == 0); + + ecs_fini(world); +} + +void Lookup_lookup_this() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t lookup = ecs_lookup(world, "."); + test_assert(lookup != 0); + test_assert(lookup == EcsThis); + + ecs_fini(world); +} + +void Lookup_lookup_wildcard() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t lookup = ecs_lookup(world, "*"); + test_assert(lookup != 0); + test_assert(lookup == EcsWildcard); + + ecs_fini(world); +} + +void Lookup_lookup_path_this() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t lookup = ecs_lookup_path_w_sep(world, 0, ".", NULL, NULL, false); + test_assert(lookup != 0); + test_assert(lookup == EcsThis); + + ecs_fini(world); +} + +void Lookup_lookup_path_wildcard() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t lookup = ecs_lookup_path_w_sep(world, 0, "*", NULL, NULL, false); + test_assert(lookup != 0); + test_assert(lookup == EcsWildcard); + + ecs_fini(world); +} + +void Lookup_lookup_path_this_from_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_new_id(world); + + ecs_entity_t lookup = ecs_lookup_path_w_sep(world, scope, ".", NULL, NULL, false); + test_assert(lookup != 0); + test_assert(lookup == EcsThis); + + ecs_fini(world); +} + +void Lookup_lookup_path_wildcard_from_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_new_id(world); + + ecs_entity_t lookup = ecs_lookup_path_w_sep(world, scope, ".", NULL, NULL, false); + test_assert(lookup != 0); + test_assert(lookup == EcsThis); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Modules.c b/fggl/ecs2/flecs/test/api/src/Modules.c new file mode 100644 index 0000000000000000000000000000000000000000..d8116ed0821a695f24eb9d4c8266bc99fd3362b0 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Modules.c @@ -0,0 +1,404 @@ +#include <api.h> + +void Modules_setup() { + ecs_tracing_enable(-3); +} + +/* -- Begin module code -- */ + +typedef struct SimpleFooComponent { + float value; +} SimpleFooComponent; + + +typedef struct NestedComponent { + float value; +} NestedComponent; + +static +void Move(ecs_iter_t *it) { } + +static +void SimpleFooSystem(ecs_iter_t *it) { } + +static +void SimpleFooTrigger(ecs_iter_t *it) { } + +typedef struct NestedModule { + ECS_DECLARE_COMPONENT(NestedComponent); +} NestedModule; + +typedef struct SimpleModule { + ECS_DECLARE_COMPONENT(Position); + ECS_DECLARE_COMPONENT(Velocity); + ECS_DECLARE_COMPONENT(SimpleFooComponent); + ECS_DECLARE_ENTITY(Tag); + ECS_DECLARE_ENTITY(SimpleFooTag); + ECS_DECLARE_ENTITY(Entity); + ECS_DECLARE_ENTITY(SimpleFooEntity); + ECS_DECLARE_ENTITY(Move); + ECS_DECLARE_ENTITY(SimpleFooSystem); + ECS_DECLARE_TYPE(SimpleFooType); + ECS_DECLARE_ENTITY(SimpleFooPrefab); + ECS_DECLARE_ENTITY(SimpleFooPipeline); + ECS_DECLARE_ENTITY(SimpleFooTrigger); + ECS_DECLARE_ENTITY(Simple_underscore); +} SimpleModule; + +#define NestedModuleImportHandles(handles)\ + ECS_IMPORT_COMPONENT(handles, NestedComponent);\ + +#define SimpleModuleImportHandles(handles)\ + ECS_IMPORT_COMPONENT(handles, Position);\ + ECS_IMPORT_COMPONENT(handles, Velocity);\ + ECS_IMPORT_COMPONENT(handles, SimpleFooComponent);\ + ECS_IMPORT_ENTITY(handles, Tag);\ + ECS_IMPORT_ENTITY(handles, SimpleFooTag);\ + ECS_IMPORT_ENTITY(handles, Entity);\ + ECS_IMPORT_ENTITY(handles, SimpleFooEntity);\ + ECS_IMPORT_ENTITY(handles, Move);\ + ECS_IMPORT_ENTITY(handles, SimpleFooSystem);\ + ECS_IMPORT_TYPE(handles, SimpleFooType);\ + ECS_IMPORT_ENTITY(handles, SimpleFooPrefab);\ + ECS_IMPORT_ENTITY(handles, SimpleFooPipeline);\ + ECS_IMPORT_ENTITY(handles, SimpleFooTrigger);\ + ECS_IMPORT_ENTITY(handles, Simple_underscore); + +void NestedModuleImport( + ecs_world_t *world) +{ + ECS_MODULE(world, NestedModule); + + ecs_set_name_prefix(world, "Nested"); + + ECS_COMPONENT(world, NestedComponent); + + ECS_EXPORT_COMPONENT(NestedComponent); +} + +void SimpleModuleImport( + ecs_world_t *world) +{ + ECS_MODULE(world, SimpleModule); + + ECS_IMPORT(world, NestedModule); + + ecs_set_name_prefix(world, "Simple"); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, SimpleFooComponent); + + ECS_TAG(world, Tag); + ECS_ENTITY(world, Entity, 0); + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + ECS_TAG(world, SimpleFooTag); + ECS_ENTITY(world, SimpleFooEntity, 0); + ECS_PREFAB(world, SimpleFooPrefab, 0); + ECS_TYPE(world, SimpleFooType, Position, Velocity); + ECS_SYSTEM(world, SimpleFooSystem, EcsOnUpdate, Position); + ECS_PIPELINE(world, SimpleFooPipeline, Tag); + ECS_TRIGGER(world, SimpleFooTrigger, EcsOnAdd, Position); + ECS_TAG(world, Simple_underscore); + + ECS_EXPORT_COMPONENT(Position); + ECS_EXPORT_COMPONENT(Velocity); + ECS_EXPORT_COMPONENT(SimpleFooComponent); + ECS_EXPORT_ENTITY(Tag); + ECS_EXPORT_ENTITY(SimpleFooTag); + ECS_EXPORT_ENTITY(Entity); + ECS_EXPORT_ENTITY(SimpleFooEntity); + ECS_EXPORT_ENTITY(Move); + ECS_EXPORT_ENTITY(SimpleFooSystem); + ECS_EXPORT_ENTITY(SimpleFooPrefab); + ECS_EXPORT_TYPE(SimpleFooType); + ECS_EXPORT_ENTITY(SimpleFooPipeline); + ECS_EXPORT_ENTITY(SimpleFooTrigger); + ECS_EXPORT_ENTITY(Simple_underscore); +} + +/* -- End module code -- */ + +void Modules_simple_module() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + ecs_add(world, e, Velocity); + test_assert( ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +static +void AddVtoP(ecs_iter_t *it) { + ECS_IMPORT_COLUMN(it, SimpleModule, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +void Modules_import_module_from_system() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + ECS_SYSTEM(world, AddVtoP, EcsOnUpdate, simple.module.Position, simple.module:simple.module); + + const void *module_ptr = ecs_get(world, ecs_id(SimpleModule), SimpleModule); + test_assert(module_ptr != NULL); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +ecs_entity_t import_module(ecs_world_t *world) { + ECS_IMPORT(world, SimpleModule); + return ecs_id(SimpleModule); +} + +void Modules_import_again() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + test_assert(ecs_id(SimpleModule) != 0); + test_assert(ecs_id(SimpleModule) == import_module(world)); + + ecs_fini(world); +} + +void Modules_scoped_component() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.Position"); + test_assert(e != 0); + test_assert(e == ecs_id(Position)); + + ecs_fini(world); +} + +void Modules_scoped_tag() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.Tag"); + test_assert(e != 0); + test_assert(e == Tag); + + ecs_fini(world); +} + +void Modules_scoped_system() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.Move"); + test_assert(e != 0); + test_assert(e == Move); + + ecs_fini(world); +} + +void Modules_scoped_entity() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.Entity"); + test_assert(e != 0); + test_assert(e == Entity); + + ecs_fini(world); +} + +void Modules_name_prefix_component() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooComponent"); + test_assert(e != 0); + test_assert(e == ecs_id(SimpleFooComponent)); + + ecs_fini(world); +} + +void Modules_name_prefix_tag() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooTag"); + test_assert(e != 0); + test_assert(e == SimpleFooTag); + + ecs_fini(world); +} + +void Modules_name_prefix_system() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooSystem"); + test_assert(e != 0); + test_assert(e == SimpleFooSystem); + + ecs_fini(world); +} + +void Modules_name_prefix_entity() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooEntity"); + test_assert(e != 0); + test_assert(e == SimpleFooEntity); + + ecs_fini(world); +} + +void Modules_name_prefix_type() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooType"); + test_assert(e != 0); + test_assert(e == SimpleFooType); + + ecs_fini(world); +} + +void Modules_name_prefix_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooPrefab"); + test_assert(e != 0); + test_assert(e == SimpleFooPrefab); + + ecs_fini(world); +} + +void Modules_name_prefix_pipeline() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooPipeline"); + test_assert(e != 0); + test_assert(e == SimpleFooPipeline); + + ecs_fini(world); +} + +void Modules_name_prefix_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.FooTrigger"); + test_assert(e != 0); + test_assert(e == SimpleFooTrigger); + + ecs_fini(world); +} + +void Modules_name_prefix_underscore() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "simple.module.underscore"); + test_assert(e != 0); + test_assert(e == Simple_underscore); + + ecs_fini(world); +} + +void Modules_lookup_by_symbol() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_symbol(world, "Position", true); + test_assert(e != 0); + test_assert(e == ecs_id(Position)); + + e = ecs_lookup_symbol(world, "SimpleFooComponent", true); + test_assert(e != 0); + test_assert(e == ecs_id(SimpleFooComponent)); + + e = ecs_lookup_symbol(world, "Simple_underscore", true); + test_assert(e != 0); + test_assert(e == Simple_underscore); + + ecs_fini(world); +} + +void Modules_import_type() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_type_t type = ecs_type(SimpleFooType); + test_assert(type != NULL); + + test_int(ecs_vector_count(type), 2); + test_assert(ecs_type_has_entity(world, type, ecs_id(Position))); + test_assert(ecs_type_has_entity(world, type, ecs_id(Velocity))); + + ecs_fini(world); +} + +void Modules_nested_module() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t e = ecs_lookup_fullpath(world, "nested.module.Component"); + test_assert(e != 0); + + char *path = ecs_get_fullpath(world, e); + test_str(path, "nested.module.Component"); + ecs_os_free(path); + + ecs_fini(world); +} + +void Modules_module_tag_on_namespace() { + ecs_world_t *world = ecs_init(); + + ECS_IMPORT(world, SimpleModule); + + ecs_entity_t mid = ecs_lookup_fullpath(world, "simple.module"); + test_assert(mid != 0); + test_assert(ecs_has_id(world, mid, EcsModule)); + + ecs_entity_t nid = ecs_lookup_fullpath(world, "simple"); + test_assert(nid != 0); + test_assert(ecs_has_id(world, nid, EcsModule)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Monitor.c b/fggl/ecs2/flecs/test/api/src/Monitor.c new file mode 100644 index 0000000000000000000000000000000000000000..6d3b85442c9c5e802c6ef0295b49aefae0c36617 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Monitor.c @@ -0,0 +1,261 @@ +#include <api.h> + +static +void OnPosition(ecs_iter_t *it) { + probe_system(it); +} + +void Monitor_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ctx = (Probe){ 0 }; + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Monitor_2_comps() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void Monitor_1_comp_1_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, Position, !Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ctx = (Probe){ 0 }; + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Velocity); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void Monitor_1_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, PARENT:Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t parent = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsChildOf, parent); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], parent); + + ecs_fini(world); +} + +void Monitor_1_comp_1_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, Position, PARENT:Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t parent = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsChildOf, parent); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], parent); + + ctx = (Probe){ 0 }; + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], parent); + + ecs_fini(world); +} + +void Monitor_1_comp_prefab_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ECS_PREFAB(world, Prefab, Position); + + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_add(world, Prefab, Velocity); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Monitor_1_comp_prefab_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsMonitor, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_w_entity(world, EcsPrefab); + ecs_add(world, e, Position); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/MultiThread.c b/fggl/ecs2/flecs/test/api/src/MultiThread.c new file mode 100644 index 0000000000000000000000000000000000000000..064de2dfa76594a790b9171917adee22c57427a0 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/MultiThread.c @@ -0,0 +1,923 @@ +#include <api.h> + +void MultiThread_setup() { + bake_set_os_api(); + ecs_tracing_enable(-3); +} + +void Progress(ecs_iter_t *it) { + Position *pos = ecs_term(it, Position, 1); + int row; + for (row = 0; row < it->count; row ++) { + Position *p = &pos[row]; + p->x ++; + } +} + +void MultiThread_2_thread_10_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 10, THREADS = 2; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_2_thread_1_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 1, THREADS = 2; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + +void MultiThread_2_thread_2_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 2, THREADS = 2; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + +void MultiThread_2_thread_5_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 5, THREADS = 2; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_3_thread_10_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 10, THREADS = 3; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_3_thread_1_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 1, THREADS = 3; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_3_thread_2_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 2, THREADS = 3; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_3_thread_5_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 5, THREADS = 3; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_4_thread_10_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 10, THREADS = 4; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_4_thread_1_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 1, THREADS = 4; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_4_thread_2_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 2, THREADS = 4; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_4_thread_5_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 5, THREADS = 4; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_5_thread_10_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 10, THREADS = 5; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_5_thread_1_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 1, THREADS = 5; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_5_thread_2_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 2, THREADS = 5; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_5_thread_5_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 5, THREADS = 5; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_6_thread_10_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 10, THREADS = 6; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_6_thread_1_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 1, THREADS = 6; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_6_thread_2_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 2, THREADS = 6; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + + +void MultiThread_6_thread_5_entity() { + ecs_world_t *world = ecs_init(); + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Progress, EcsOnUpdate, Position); + + int i, ENTITIES = 5, THREADS = 6; + ecs_entity_t *handles = ecs_os_alloca(sizeof(ecs_entity_t) * ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + handles[i] = ecs_new(world, Position); + ecs_set(world, handles[i], Position, {0}); + } + + ecs_set_threads(world, THREADS); + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 1); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + test_int(ecs_get(world, handles[i], Position)->x, 2); + } + + ecs_fini(world); +} + +typedef struct Param { + ecs_entity_t entity; + int count; +} Param; + +static +void TestSubset(ecs_iter_t *it) { + Param *param = it->param; + + int i; + for (i = 0; i < it->count; i ++) { + test_assert(param->entity != it->entities[i]); + param->count ++; + } +} + +static +void TestAll(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + ecs_entity_t TestSubset = ecs_term_id(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + Param param = {.entity = it->entities[i], 0}; + ecs_run_w_filter(it->world, TestSubset, 1, it->frame_offset + i + 1, 0, 0, ¶m); + p[i].x += param.count; + } +} + +static +void test_combs_100_entity(int THREADS) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TestSubset, 0, Position); + ECS_SYSTEM(world, TestAll, EcsOnUpdate, Position, :TestSubset); + + int i, ENTITIES = 100; + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + ecs_set(world, ids[i], Position, {1, 2}); + } + + ecs_set_threads(world, THREADS); + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + const Position *p = ecs_get(world, ids[i], Position); + test_int(p->x, ENTITIES - i); + } + + ecs_fini(world); +} + +void MultiThread_2_thread_test_combs_100_entity_w_next_worker() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TestSubset, 0, Position); + + ecs_query_t *q = ecs_query_new(world, "Position, :TestSubset"); + + int i, ENTITIES = 4; + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + ecs_set(world, ids[i], Position, {1, 2}); + } + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next_worker(&it, 0, 2)) { + TestAll(&it); + } + + it = ecs_query_iter(q); + while (ecs_query_next_worker(&it, 1, 2)) { + TestAll(&it); + } + + for (i = 0; i < ENTITIES; i ++) { + const Position *p = ecs_get(world, ids[i], Position); + test_int(p->x, ENTITIES - i); + } + + ecs_fini(world); +} + +void MultiThread_2_thread_test_combs_100_entity() { + test_combs_100_entity(2); +} + +void MultiThread_3_thread_test_combs_100_entity() { + test_combs_100_entity(3); +} + +void MultiThread_4_thread_test_combs_100_entity() { + test_combs_100_entity(4); +} + +void MultiThread_5_thread_test_combs_100_entity() { + test_combs_100_entity(5); +} + +void MultiThread_6_thread_test_combs_100_entity() { + test_combs_100_entity(6); +} + +static +void test_combs_100_entity_2_types(int THREADS) { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, TestSubset, 0, Position); + ECS_SYSTEM(world, TestAll, EcsOnUpdate, Position, :TestSubset); + + int i, ENTITIES = 100; + + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, ENTITIES / 2); + ecs_entity_t ids_1[50]; + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * ENTITIES / 2); + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, ENTITIES / 2); + + for (i = 0; i < ENTITIES / 2; i ++) { + ecs_set(world, ids_1[i], Position, {1, 2}); + ecs_set(world, ids_2[i], Position, {1, 2}); + } + + ecs_set_threads(world, THREADS); + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES / 2; i ++) { + const Position *p = ecs_get(world, ids_1[i], Position); + test_int(p->x, ENTITIES - i); + + p = ecs_get(world, ids_2[i], Position); + test_int(p->x, ENTITIES - (i + ENTITIES / 2)); + } + + ecs_fini(world); +} + +void MultiThread_2_thread_test_combs_100_entity_2_types() { + test_combs_100_entity_2_types(2); +} + +void MultiThread_3_thread_test_combs_100_entity_2_types() { + test_combs_100_entity_2_types(3); +} + +void MultiThread_4_thread_test_combs_100_entity_2_types() { + test_combs_100_entity_2_types(4); +} + +void MultiThread_5_thread_test_combs_100_entity_2_types() { + test_combs_100_entity_2_types(5); +} + +void MultiThread_6_thread_test_combs_100_entity_2_types() { + test_combs_100_entity_2_types(6); +} + +void MultiThread_change_thread_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, TestSubset, 0, Position); + ECS_SYSTEM(world, TestAll, EcsOnUpdate, Position, :TestSubset); + + int i, ENTITIES = 100; + + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, ENTITIES / 2); + ecs_entity_t ids_1[50]; + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * ENTITIES / 2); + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, ENTITIES / 2); + + for (i = 0; i < ENTITIES / 2; i ++) { + ecs_set(world, ids_1[i], Position, {1, 2}); + ecs_set(world, ids_2[i], Position, {1, 2}); + } + + ecs_set_threads(world, 2); + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES / 2; i ++) { + Position *p = ecs_get_mut(world, ids_1[i], Position, NULL); + test_int(p->x, ENTITIES - i); + p->x = 1; + + p = ecs_get_mut(world, ids_2[i], Position, NULL); + test_int(p->x, ENTITIES - (i + ENTITIES / 2)); + p->x = 1; + } + + ecs_set_threads(world, 3); + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES / 2; i ++) { + const Position *p = ecs_get(world, ids_1[i], Position); + test_int(p->x, ENTITIES - i); + + p = ecs_get(world, ids_2[i], Position); + test_int(p->x, ENTITIES - (i + ENTITIES / 2)); + } + + ecs_fini(world); +} + +static +void QuitSystem(ecs_iter_t *it) { + ecs_quit(it->world); +} + +void MultiThread_multithread_quit() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, QuitSystem, EcsOnUpdate, Position); + + ecs_bulk_new(world, Position, 100); + + ecs_set_threads(world, 2); + + test_assert( ecs_progress(world, 0) == 0); + + ecs_fini(world); +} + +static bool has_ran = false; + +static +void MtTask(ecs_iter_t *it) { + has_ran = true; +} + +void MultiThread_schedule_w_tasks() { + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, MtTask, EcsOnUpdate, 0); + + ecs_set_threads(world, 2); + + test_assert( ecs_progress(world, 0) != 0); + + test_assert( has_ran == true); + + ecs_fini(world); +} + +static +void ReactiveDummySystem(ecs_iter_t * it) { + has_ran = true; +} + +static +void PeriodicDummySystem(ecs_iter_t * it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + int i; + for (i = 0; i < it->count; i++ ) { + ecs_set(it->world, it->entities[i], Position, {0}); + } +} + +void MultiThread_reactive_system() { + ecs_world_t * world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, PeriodicDummySystem, EcsOnUpdate, Position); + ECS_SYSTEM(world, ReactiveDummySystem, EcsOnSet, Position); + + ecs_bulk_new(world, Position, 2); + ecs_set_threads(world, 2); + + test_assert(has_ran == false); + + ecs_progress(world, 0); + + test_assert(has_ran == true); + + ecs_fini(world); +} + +void MultiThread_fini_after_set_threads() { + ecs_world_t * world = ecs_init(); + + ecs_set_threads(world, 2); + + ecs_fini(world); + + // Make sure code doesn't crash + test_assert(true); +} + diff --git a/fggl/ecs2/flecs/test/api/src/MultiThreadStaging.c b/fggl/ecs2/flecs/test/api/src/MultiThreadStaging.c new file mode 100644 index 0000000000000000000000000000000000000000..15e55fa9be0097ca44f944cb4cb4bc016d87fc7d --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/MultiThreadStaging.c @@ -0,0 +1,467 @@ +#include <api.h> + +void MultiThreadStaging_setup() { + bake_set_os_api(); + ecs_tracing_enable(-3); +} + +static +void Add_to_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + } + + if (ctx->component_2) { + ecs_add_id(it->world, it->entities[i], ctx->component_2); + } + + ctx->entity_count ++; + } +} + +void MultiThreadStaging_2_threads_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_entity_t ids_1[100]; + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, 100); + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * 100); + + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, 100); + + ecs_set_threads(world, 2); + + ecs_progress(world, 1); + + int i; + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_1[i], Position)); + test_assert( ecs_has(world, ids_1[i], Rotation)); + test_assert( !ecs_has(world, ids_1[i], Velocity)); + } + + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_2[i], Position)); + test_assert( ecs_has(world, ids_2[i], Rotation)); + test_assert( ecs_has(world, ids_2[i], Velocity)); + } + + ecs_fini(world); +} + +void MultiThreadStaging_3_threads_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_entity_t ids_1[100]; + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, 100); + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * 100); + + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, 100); + + ecs_set_threads(world, 3); + + ecs_progress(world, 1); + + int i; + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_1[i], Position)); + test_assert( ecs_has(world, ids_1[i], Rotation)); + test_assert( !ecs_has(world, ids_1[i], Velocity)); + } + + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_2[i], Position)); + test_assert( ecs_has(world, ids_2[i], Rotation)); + test_assert( ecs_has(world, ids_2[i], Velocity)); + } + + ecs_fini(world); +} + +void MultiThreadStaging_4_threads_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_entity_t ids_1[100]; + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, 100); + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * 100); + + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, 100); + + ecs_set_threads(world, 4); + + ecs_progress(world, 1); + + int i; + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_1[i], Position)); + test_assert( ecs_has(world, ids_1[i], Rotation)); + test_assert( !ecs_has(world, ids_1[i], Velocity)); + } + + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_2[i], Position)); + test_assert( ecs_has(world, ids_2[i], Rotation)); + test_assert( ecs_has(world, ids_2[i], Velocity)); + } + + ecs_fini(world); +} + +void MultiThreadStaging_5_threads_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_entity_t ids_1[100]; + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, 100); + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * 100); + + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, 100); + + ecs_set_threads(world, 5); + + ecs_progress(world, 1); + + int i; + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_1[i], Position)); + test_assert( ecs_has(world, ids_1[i], Rotation)); + test_assert( !ecs_has(world, ids_1[i], Velocity)); + } + + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_2[i], Position)); + test_assert( ecs_has(world, ids_2[i], Rotation)); + test_assert( ecs_has(world, ids_2[i], Velocity)); + } + + ecs_fini(world); +} + +void MultiThreadStaging_6_threads_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_entity_t ids_1[100]; + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, 100); + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * 100); + + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, 100); + + ecs_set_threads(world, 6); + + ecs_progress(world, 1); + + int i; + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_1[i], Position)); + test_assert( ecs_has(world, ids_1[i], Rotation)); + test_assert( !ecs_has(world, ids_1[i], Velocity)); + } + + for (i = 0; i < 100; i ++) { + test_assert( ecs_has(world, ids_2[i], Position)); + test_assert( ecs_has(world, ids_2[i], Rotation)); + test_assert( ecs_has(world, ids_2[i], Velocity)); + } + + ecs_fini(world); +} + +static +void InitVelocity(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + int i; + for (i = 0; i < it->count; i ++) { + v[i].x = 10; + v[i].y = 20; + } +} + +static +void AddVelocity(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +void MultiThreadStaging_2_threads_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TRIGGER(world, InitVelocity, EcsOnAdd, Velocity); + ECS_SYSTEM(world, AddVelocity, EcsOnUpdate, Position, :Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + ecs_set_threads(world, 2); + + ecs_progress(world, 0); + + int i; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + test_assert( ecs_has(world, e, Velocity)); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + } + + ecs_fini(world); +} + +static +void New_w_count(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + ecs_bulk_new(it->world, Position, 10); +} + +void MultiThreadStaging_new_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, New_w_count, EcsOnUpdate, :Position); + + ecs_set_threads(world, 2); + + ecs_progress(world, 0); + + test_int( ecs_count(world, Position), 10); + + ecs_fini(world); +} + +void MultiThreadStaging_custom_thread_auto_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_set_stages(world, 2); + + ecs_world_t *ctx_1 = ecs_get_stage(world, 0); + ecs_world_t *ctx_2 = ecs_get_stage(world, 1); + + ecs_frame_begin(world, 0); + ecs_staging_begin(world); + + /* thread 1 */ + ecs_defer_begin(ctx_1); + ecs_set(ctx_1, e1, Position, {10, 20}); + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(ctx_1, e1, Position)); + ecs_defer_end(ctx_1); + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(ctx_1, e1, Position)); + + /* thread 2 */ + ecs_defer_begin(ctx_2); + ecs_set(ctx_2, e2, Position, {20, 30}); + test_assert(!ecs_has(world, e2, Position)); + test_assert(!ecs_has(ctx_2, e2, Position)); + ecs_defer_end(ctx_2); + test_assert(!ecs_has(world, e2, Position)); + test_assert(!ecs_has(ctx_2, e2, Position)); + + ecs_staging_end(world); + ecs_frame_end(world); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + + const Position *p1 = ecs_get(world, e1, Position); + test_int(p1->x, 10); + test_int(p1->y, 20); + + const Position *p2 = ecs_get(world, e2, Position); + test_int(p2->x, 20); + test_int(p2->y, 30); + + ecs_fini(world); +} + +void MultiThreadStaging_custom_thread_manual_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_set_automerge(world, false); + + ecs_set_stages(world, 2); + + ecs_world_t *ctx_1 = ecs_get_stage(world, 0); + ecs_world_t *ctx_2 = ecs_get_stage(world, 1); + + ecs_frame_begin(world, 0); + ecs_staging_begin(world); + + /* thread 1 */ + ecs_defer_begin(ctx_1); + ecs_set(ctx_1, e1, Position, {10, 20}); + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(ctx_1, e1, Position)); + ecs_defer_end(ctx_1); + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(ctx_1, e1, Position)); + + /* thread 2 */ + ecs_defer_begin(ctx_2); + ecs_set(ctx_2, e2, Position, {20, 30}); + test_assert(!ecs_has(world, e2, Position)); + test_assert(!ecs_has(ctx_2, e2, Position)); + ecs_defer_end(ctx_2); + test_assert(!ecs_has(world, e2, Position)); + test_assert(!ecs_has(ctx_2, e2, Position)); + + ecs_staging_end(world); + ecs_frame_end(world); + + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(world, e2, Position)); + + ecs_merge(world); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + + const Position *p1 = ecs_get(world, e1, Position); + test_int(p1->x, 10); + test_int(p1->y, 20); + + const Position *p2 = ecs_get(world, e2, Position); + test_int(p2->x, 20); + test_int(p2->y, 30); + + ecs_fini(world); +} + +void MultiThreadStaging_custom_thread_partial_manual_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_set_stages(world, 2); + + ecs_world_t *ctx_1 = ecs_get_stage(world, 0); + ecs_world_t *ctx_2 = ecs_get_stage(world, 1); + + /* Only disable automerging for ctx_2 */ + ecs_set_automerge(ctx_2, false); + + ecs_frame_begin(world, 0); + ecs_staging_begin(world); + + /* thread 1 */ + ecs_defer_begin(ctx_1); + ecs_set(ctx_1, e1, Position, {10, 20}); + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(ctx_1, e1, Position)); + ecs_defer_end(ctx_1); + test_assert(!ecs_has(world, e1, Position)); + test_assert(!ecs_has(ctx_1, e1, Position)); + + /* thread 2 */ + ecs_defer_begin(ctx_2); + ecs_set(ctx_2, e2, Position, {20, 30}); + test_assert(!ecs_has(world, e2, Position)); + test_assert(!ecs_has(ctx_2, e2, Position)); + ecs_defer_end(ctx_2); + test_assert(!ecs_has(world, e2, Position)); + test_assert(!ecs_has(ctx_2, e2, Position)); + + ecs_staging_end(world); + ecs_frame_end(world); + + test_assert(ecs_has(world, e1, Position)); + test_assert(!ecs_has(world, e2, Position)); + + const Position *p1 = ecs_get(world, e1, Position); + test_int(p1->x, 10); + test_int(p1->y, 20); + + ecs_merge(ctx_2); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Position)); + + p1 = ecs_get(world, e1, Position); + test_int(p1->x, 10); + test_int(p1->y, 20); + + const Position *p2 = ecs_get(world, e2, Position); + test_int(p2->x, 20); + test_int(p2->y, 30); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/New.c b/fggl/ecs2/flecs/test/api/src/New.c new file mode 100644 index 0000000000000000000000000000000000000000..1698e6c3ac14b50e748b7a1d3bc1923702950a6b --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/New.c @@ -0,0 +1,621 @@ +#include <api.h> + +void New_setup() { + ecs_tracing_enable(-2); +} + +void New_empty() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(!ecs_get_type(world, e)); + + ecs_fini(world); +} + +void New_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void New_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, Position); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void New_type_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void New_type_w_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, AND | Type_1); + + ecs_entity_t e = ecs_new(world, Type_2); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void New_type_w_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, Velocity); + ECS_TYPE(world, Type_3, AND | Type_1, AND | Type_2); + + ecs_entity_t e = ecs_new(world, Type_3); + + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void New_type_mixed() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, AND | Type_1, Velocity); + + ecs_entity_t e = ecs_new(world, Type_2); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void New_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + + ecs_entity_t e = ecs_new_w_entity(world, Tag); + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag)); + + ecs_fini(world); +} + +void New_type_w_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + + ECS_TYPE(world, Type, Tag); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag)); + + ecs_fini(world); +} + +void New_type_w_2_tags() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + + ECS_TYPE(world, Type, Tag_1, Tag_2); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_1)); + + ecs_fini(world); +} + +void New_type_w_tag_mixed() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_COMPONENT(world, Position); + + ECS_TYPE(world, Type, Position, Tag); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag)); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void New_redefine_component() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = 0; + + { + ECS_COMPONENT(world, Position); + c = ecs_id(Position); + } + + { + ECS_COMPONENT(world, Position); + test_assert(c == ecs_id(Position)); + } + + ecs_fini(world); +} + +void New_recycle_id_empty() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + ecs_delete(world, e1); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + test_assert(e1 != e2); + test_assert((e1 & ECS_ENTITY_MASK) == (e2 & ECS_ENTITY_MASK)); + + ecs_fini(world); +} + +void New_recycle_id_w_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t tag = ecs_new(world, 0); + + ecs_entity_t e1 = ecs_new_w_entity(world, tag); + test_assert(e1 != 0); + ecs_delete(world, e1); + + ecs_entity_t e2 = ecs_new_w_entity(world, tag); + test_assert(e2 != 0); + test_assert(e1 != e2); + test_assert((e1 & ECS_ENTITY_MASK) == (e2 & ECS_ENTITY_MASK)); + + ecs_fini(world); +} + +void New_recycle_id_w_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new_w_type(world, ecs_type(Position)); + test_assert(e1 != 0); + ecs_delete(world, e1); + + ecs_entity_t e2 = ecs_new_w_type(world, ecs_type(Position)); + test_assert(e2 != 0); + test_assert(e1 != e2); + test_assert((e1 & ECS_ENTITY_MASK) == (e2 & ECS_ENTITY_MASK)); + + ecs_fini(world); +} + +void New_recycle_empty_staged_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_defer_begin(world); + ecs_delete(world, e1); + ecs_defer_end(world); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + test_assert(e1 != e2); + test_assert((e1 & ECS_ENTITY_MASK) == (e2 & ECS_ENTITY_MASK)); + + ecs_fini(world); +} + +void New_recycle_staged_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + + ecs_defer_begin(world); + ecs_delete(world, e1); + ecs_defer_end(world); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + test_assert(e1 != e2); + test_assert((e1 & ECS_ENTITY_MASK) == (e2 & ECS_ENTITY_MASK)); + + ecs_fini(world); +} + +void New_new_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + test_assert(!ecs_get_type(world, e)); + + ecs_fini(world); +} + +void New_new_component_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_component_id(world); + test_assert(e != 0); + test_assert(e < ECS_HI_COMPONENT_ID); + test_assert(!ecs_get_type(world, e)); + + ecs_fini(world); +} + +void New_new_component_id_skip_used() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + + ecs_entity_t e = ecs_new_component_id(world); + test_assert(e != 0); + test_assert(e < ECS_HI_COMPONENT_ID); + test_assert(!ecs_get_type(world, e)); + + /* Explicitly set an id that is one above the last issued id */ + ecs_add_id(world, e + 1, Foo); + + ecs_entity_t e2 = ecs_new_component_id(world); + test_assert(e2 != 0); + test_assert(e2 < ECS_HI_COMPONENT_ID); + test_assert(!ecs_get_type(world, e2)); + test_assert(e2 != (e + 1)); + + ecs_fini(world); +} + +void New_new_component_id_skip_to_hi_id() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + + ecs_entity_t e = ecs_new_component_id(world); + test_assert(e != 0); + + /* Use up all low component ids */ + int i; + for (i = (int)e; i < ECS_HI_COMPONENT_ID; i ++) { + ecs_add_id(world, i, Foo); + } + + ecs_entity_t e2 = ecs_new_component_id(world); + test_assert(e2 != 0); + test_assert(e2 > ECS_HI_COMPONENT_ID); + test_assert(!ecs_get_type(world, e2)); + + ecs_entity_t e3 = ecs_new_id(world); + test_assert(e3 != e2); + test_assert(e3 > e2); + test_assert(!ecs_get_type(world, e3)); + + ecs_fini(world); +} + +void New_new_hi_component_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + ecs_entity_t c; + do { + c = ecs_new_component_id(world); + } while (c < ECS_HI_COMPONENT_ID); + + test_assert(c != e); + test_assert(c > e); + + ecs_fini(world); +} + +void New_new_w_entity_0() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_w_entity(world, 0); + test_assert(e != 0); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +ECS_ENTITY_DECLARE(Foo); + +void New_create_w_explicit_id_2_worlds() { + ecs_world_t *world_1 = ecs_init(); + ecs_world_t *world_2 = ecs_init(); + + ecs_entity_t p1 = ecs_set_name(world_1, 0, "Parent"); + ecs_entity_t p2 = ecs_set_name(world_2, 0, "Parent"); + + ecs_set_scope(world_1, p1); + ecs_set_scope(world_2, p2); + + ECS_ENTITY_DEFINE(world_1, Foo, 0); + ECS_ENTITY_DEFINE(world_2, Foo, 0); + + char *path = ecs_get_fullpath(world_1, Foo); + test_str(path, "Parent.Foo"); + ecs_os_free(path); + + path = ecs_get_fullpath(world_2, Foo); + test_str(path, "Parent.Foo"); + ecs_os_free(path); + + ecs_fini(world_1); + ecs_fini(world_2); +} + +void New_new_w_id_0_w_with() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_set_with(world, Tag); + + ecs_entity_t e = ecs_new_w_id(world, 0); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void New_new_w_id_w_with() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + + ecs_set_with(world, Tag); + + ecs_entity_t e = ecs_new_w_id(world, Tag2); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Tag2)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void New_new_w_id_w_with_w_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + + ecs_set_with(world, Tag); + + ecs_entity_t parent = ecs_new_id(world); + ecs_set_scope(world, parent); + + ecs_entity_t e = ecs_new_w_id(world, Tag2); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Tag2)); + test_assert(ecs_has_pair(world, e, EcsChildOf, parent)); + + test_int(ecs_set_with(world, 0), Tag); + test_int(ecs_set_scope(world, 0), parent); + + ecs_fini(world); +} + +void New_new_w_id_w_with_defer() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + + ecs_set_with(world, Tag); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new_w_id(world, Tag2); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Tag)); + test_assert(!ecs_has(world, e, Tag2)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Tag2)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void New_new_w_id_w_with_defer_w_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + + ecs_set_with(world, Tag); + + ecs_entity_t parent = ecs_new_id(world); + ecs_set_scope(world, parent); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new_w_id(world, Tag2); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Tag)); + test_assert(!ecs_has(world, e, Tag2)); + test_assert(!ecs_has_pair(world, e, EcsChildOf, parent)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Tag2)); + test_assert(ecs_has_pair(world, e, EcsChildOf, parent)); + + test_int(ecs_set_with(world, 0), Tag); + test_int(ecs_set_scope(world, 0), parent); + + ecs_fini(world); +} + +void New_new_w_type_0_w_with() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_set_with(world, Tag); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void New_new_w_type_w_with() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Position); + + ecs_set_with(world, Tag); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Position)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void New_new_w_type_w_with_w_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Position); + + ecs_set_with(world, Tag); + + ecs_entity_t parent = ecs_new_id(world); + ecs_set_scope(world, parent); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has_pair(world, e, EcsChildOf, parent)); + + test_int(ecs_set_with(world, 0), Tag); + test_int(ecs_set_scope(world, 0), parent); + + ecs_fini(world); +} + +void New_new_w_type_w_with_defer() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Position); + + ecs_set_with(world, Tag); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Tag)); + test_assert(!ecs_has(world, e, Position)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Position)); + + test_int(ecs_set_with(world, 0), Tag); + + ecs_fini(world); +} + +void New_new_w_type_w_with_defer_w_scope() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Position); + + ecs_set_with(world, Tag); + + ecs_entity_t parent = ecs_new_id(world); + ecs_set_scope(world, parent); + + ecs_defer_begin(world); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_assert(!ecs_has(world, e, Tag)); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has_pair(world, e, EcsChildOf, parent)); + + ecs_defer_end(world); + + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has_pair(world, e, EcsChildOf, parent)); + + test_int(ecs_set_with(world, 0), Tag); + test_int(ecs_set_scope(world, 0), parent); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/New_w_Count.c b/fggl/ecs2/flecs/test/api/src/New_w_Count.c new file mode 100644 index 0000000000000000000000000000000000000000..344f37a2bff430fc9401755abab93defc0055ea3 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/New_w_Count.c @@ -0,0 +1,718 @@ +#include <api.h> + +void New_w_Count_empty() { + ecs_world_t *world = ecs_init(); + + const ecs_entity_t *ids = ecs_bulk_new(world, 0, 1000); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 1000; i ++) { + test_assert(ids[i] != 0); + } + + ecs_fini(world); +} + +void New_w_Count_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 1000); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + } + + test_int(ecs_count(world, Position), 1000); + + ecs_fini(world); +} + +void New_w_Count_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, MyType, Position); + + const ecs_entity_t *ids = ecs_bulk_new(world, MyType, 1000); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + } + + test_int(ecs_count(world, MyType), 1000); + + ecs_fini(world); +} + +void New_w_Count_type_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, MyType, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, MyType, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, MyType), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + } + + ecs_fini(world); +} + +void New_w_Count_type_w_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, AND | Type_1); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type_2, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, Type_2), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + } + + ecs_fini(world); +} + +void New_w_Count_type_w_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, Velocity); + ECS_TYPE(world, Type_3, AND | Type_1, AND | Type_2); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type_3, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, Type_3), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + } + + ecs_fini(world); +} + +void New_w_Count_type_mixed() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type_1, Position); + ECS_TYPE(world, Type_2, AND | Type_1, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type_2, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, Type_2), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + } + + ecs_fini(world); +} + +void New_w_Count_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + + const ecs_entity_t *ids = ecs_bulk_new_w_entity(world, Tag, 1000); + test_assert(ids != NULL); + test_int(ecs_count_entity(world, Tag), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag)); + } + + ecs_fini(world); +} + +void New_w_Count_type_w_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_TYPE(world, Type, Tag); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, Type), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag)); + } + + ecs_fini(world); +} + +void New_w_Count_type_w_2_tags() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + ECS_TYPE(world, Type, Tag_1, Tag_2); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, Type), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + } + + ecs_fini(world); +} + +void New_w_Count_type_w_tag_mixed() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag_1, 0); + ECS_ENTITY(world, Tag_2, 0); + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, Tag_1, Tag_2, Position); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 1000); + test_assert(ids != NULL); + test_int(ecs_count(world, Type), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert(ecs_has_entity(world, e, Tag_1)); + test_assert(ecs_has_entity(world, e, Tag_2)); + test_assert(ecs_has(world, e, Position)); + } + + ecs_fini(world); +} + +static +void AddPosition(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + ecs_entity_t velocity = *(ecs_entity_t*)it->ctx; + + int i; + for (i = it->count - 1; i >= 0; i --) { + ecs_set_id( + it->world, it->entities[i], velocity, + sizeof(Velocity), &(Velocity){2, 3}); + } +} + +static +void SetPosition(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + ecs_entity_t rotation = *(ecs_entity_t*)it->ctx; + + int i; + for (i = it->count - 1; i >= 0; i --) { + p[i].y ++; + ecs_add_id(it->world, it->entities[i], rotation); + } +} + +void New_w_Count_new_w_data_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position)}, + .count = 1 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + } + }); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} + +void New_w_Count_new_w_data_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position), ecs_id(Velocity)}, + .count = 2 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]){ + {1, 2}, + {3, 4}, + {5, 6} + } + }); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Velocity)); + test_assert(ecs_has(world, ids[1], Velocity)); + test_assert(ecs_has(world, ids[2], Velocity)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + const Velocity * + v = ecs_get(world, ids[0], Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + + v = ecs_get(world, ids[1], Velocity); + test_int(v->x, 3); + test_int(v->y, 4); + + v = ecs_get(world, ids[2], Velocity); + test_int(v->x, 5); + test_int(v->y, 6); + + ecs_fini(world); +} + +void New_w_Count_new_w_data_w_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Position); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){Tag}, + .count = 1 + }, + (void*[]){ + NULL + }); + + test_assert(ecs_has(world, ids[0], Tag)); + test_assert(ecs_has(world, ids[1], Tag)); + test_assert(ecs_has(world, ids[2], Tag)); + + ecs_fini(world); +} + +void New_w_Count_new_w_data_w_comp_and_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Position); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position), Tag}, + .count = 2 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + NULL + }); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Tag)); + test_assert(ecs_has(world, ids[1], Tag)); + test_assert(ecs_has(world, ids[2], Tag)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} + +void New_w_Count_new_w_data_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pair); + ECS_COMPONENT(world, Position); + + ecs_entity_t pair_id = ecs_pair(Pair, ecs_id(Position)); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){pair_id}, + .count = 1 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + NULL + }); + + test_assert(ecs_has_entity(world, ids[0], pair_id)); + test_assert(ecs_has_entity(world, ids[1], pair_id)); + test_assert(ecs_has_entity(world, ids[2], pair_id)); + + const Position * + p = ecs_get_id(world, ids[0], pair_id); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get_id(world, ids[1], pair_id); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get_id(world, ids[2], pair_id); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} + +void New_w_Count_new_w_data_pair_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pair); + ECS_TAG(world, Tag); + + ecs_entity_t pair_id = ecs_pair(Pair, Tag); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){pair_id}, + .count = 1 + }, + (void*[]){ + NULL + }); + + test_assert(ecs_has_entity(world, ids[0], pair_id)); + test_assert(ecs_has_entity(world, ids[1], pair_id)); + test_assert(ecs_has_entity(world, ids[2], pair_id)); + + ecs_fini(world); +} + +ECS_CTOR(Velocity, ptr, { + ptr->x = 0; + ptr->y = 0; +}); + +void New_w_Count_new_w_data_2_comp_1_not_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_set_component_actions(world, Velocity, { + .ctor = ecs_ctor(Velocity) + }); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position), ecs_id(Velocity)}, + .count = 2 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + NULL + }); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has(world, ids[0], Velocity)); + test_assert(ecs_has(world, ids[1], Velocity)); + test_assert(ecs_has(world, ids[2], Velocity)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + const Velocity * + v = ecs_get(world, ids[0], Velocity); + test_int(v->x, 0); + test_int(v->y, 0); + + v = ecs_get(world, ids[1], Velocity); + test_int(v->x, 0); + test_int(v->y, 0); + + v = ecs_get(world, ids[2], Velocity); + test_int(v->x, 0); + test_int(v->y, 0); + + ecs_fini(world); +} + +static int32_t on_movable_count = 0; + +static +void OnMovable(ecs_iter_t *it) { + on_movable_count += it->count; +} + +void New_w_Count_new_w_on_add_on_set_monitor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + + ECS_TRIGGER(world, AddPosition, EcsOnAdd, Position); + ECS_SYSTEM(world, SetPosition, EcsOnSet, Position); + ECS_SYSTEM(world, OnMovable, EcsMonitor, Position, Velocity); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity = {AddPosition}, .ctx = &ecs_id(Velocity) + }); + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {SetPosition}, .ctx = &ecs_id(Rotation) + }); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position) + }, + .count = 1 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + } + } + ); + + test_assert(ids != NULL); + test_int(on_movable_count, 3); + + int i; + for (i = 0; i < 3; i ++) { + ecs_entity_t e = ids[i]; + test_assert(e != 0); + test_assert( ecs_has(world, e, Position) ); + test_assert( ecs_has(world, e, Velocity) ); + test_assert( ecs_has(world, e, Rotation) ); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10 + 20 * i); + test_int(p->y, 20 + i * 20 + 1); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 2); + test_int(v->y, 3); + } + + ecs_fini(world); +} + +void New_w_Count_new_w_data_override_set_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_set(world, 0, Position, {100, 200}); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position), ecs_pair(EcsIsA, base)}, + .count = 2 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + NULL + }); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has_pair(world, ids[0], EcsIsA, base)); + test_assert(ecs_has_pair(world, ids[1], EcsIsA, base)); + test_assert(ecs_has_pair(world, ids[2], EcsIsA, base)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} + +void New_w_Count_new_w_data_override_set_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pair); + ECS_COMPONENT(world, Position); + + ecs_entity_t pair_id = ecs_pair(Pair, ecs_id(Position)); + + ecs_entity_t base = ecs_new(world, 0); + Position *ptr = ecs_get_mut_w_entity(world, base, pair_id, NULL); + test_assert(ptr != NULL); + ptr->x = 100; + ptr->y = 200; + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position), ecs_pair(EcsIsA, base)}, + .count = 2 + }, + (void*[]){ + (Position[]){ + {10, 20}, + {30, 40}, + {50, 60} + }, + NULL + }); + + test_assert(ecs_has(world, ids[0], Position)); + test_assert(ecs_has(world, ids[1], Position)); + test_assert(ecs_has(world, ids[2], Position)); + + test_assert(ecs_has_pair(world, ids[0], EcsIsA, base)); + test_assert(ecs_has_pair(world, ids[1], EcsIsA, base)); + test_assert(ecs_has_pair(world, ids[2], EcsIsA, base)); + + const Position * + p = ecs_get(world, ids[0], Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, ids[1], Position); + test_int(p->x, 30); + test_int(p->y, 40); + + p = ecs_get(world, ids[2], Position); + test_int(p->x, 50); + test_int(p->y, 60); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Observer.c b/fggl/ecs2/flecs/test/api/src/Observer.c new file mode 100644 index 0000000000000000000000000000000000000000..2cbec923cfc9e90c67b66be3283402d1432e85f0 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Observer.c @@ -0,0 +1,1044 @@ +#include <api.h> + +static +void Observer(ecs_iter_t *it) { + probe_system_w_ctx(it, it->ctx); +} + +static +void Observer_w_value(ecs_iter_t *it) { + probe_system_w_ctx(it, it->ctx); + + test_int(it->count, 1); + test_int(it->column_count, 2); + test_assert(it->entities != NULL); + test_assert(it->entities[0] != 0); + + Position *p = ecs_term(it, Position, 1); + Velocity *v = ecs_term(it, Velocity, 2); + + test_int(p->x, 10); + test_int(p->y, 20); + + test_int(v->x, 1); + test_int(v->y, 2); +} + +static bool dummy_called = false; + +static +void Dummy(ecs_iter_t *it) { + dummy_called = true; +} + +void Observer_2_terms_w_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_terms_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_terms_w_on_set_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_id(Position)}, {ecs_id(Velocity)}}, + .events = {EcsOnSet}, + .callback = Observer_w_value, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_set(world, e, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.c[0][1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Observer_2_terms_w_on_remove_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_id(Position)}, {ecs_id(Velocity)}}, + .events = {EcsOnRemove}, + .callback = Observer_w_value, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_set(world, e, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.c[0][1], ecs_id(Velocity)); + + ecs_fini(world); +} + + +void Observer_2_terms_w_on_add_2nd() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_terms_w_on_remove_2nd() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_pair_terms_w_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + ecs_id_t pair_a = ecs_pair(RelA, ObjA); + ecs_id_t pair_b = ecs_pair(RelB, ObjB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{pair_a}, {pair_b}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, RelA, ObjA); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], pair_a); + test_int(ctx.c[0][1], pair_b); + + ecs_fini(world); +} + +void Observer_2_pair_terms_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + ecs_id_t pair_a = ecs_pair(RelA, ObjA); + ecs_id_t pair_b = ecs_pair(RelB, ObjB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{pair_a}, {pair_b}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, RelA, ObjA); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], pair_a); + test_int(ctx.c[0][1], pair_b); + + ecs_fini(world); +} + +void Observer_2_wildcard_pair_terms_w_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, RelA, ObjA); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); + + ecs_fini(world); +} + +void Observer_2_wildcard_pair_terms_w_on_add_2_matching() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, RelA, ObjA); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, RelB, ObjA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjA)); + + ctx = (Probe){ 0 }; + ecs_add_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); + + ecs_fini(world); +} + +void Observer_2_wildcard_pair_terms_w_on_add_3_matching() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + ECS_TAG(world, ObjC); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, RelA, ObjA); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, RelB, ObjA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjA)); + + ctx = (Probe){ 0 }; + ecs_add_pair(world, e, RelB, ObjC); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjC)); + + ctx = (Probe){ 0 }; + ecs_add_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); + + ecs_fini(world); +} + + +void Observer_2_wildcard_pair_terms_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, RelA, ObjA); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, RelB, ObjB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); + test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); + + ecs_fini(world); +} + +void Observer_2_terms_1_not_w_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB, .oper = EcsNot}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_clear(world, e); + test_assert(!ecs_has(world, e, TagB)); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_terms_1_not_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB, .oper = EcsNot}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_clear(world, e); + test_assert(!ecs_has(world, e, TagB)); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_terms_w_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_id(Position)}, {TagB}}, + .events = {EcsOnSet}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_2_terms_w_un_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ecs_id(Position)}, {TagB}}, + .events = {EcsUnSet}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsUnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} + +void Observer_3_terms_2_or_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB, .oper = EcsOr}, {TagC, .oper = EcsOr}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ctx = (Probe){0}; + + ecs_add_id(world, e, TagC); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagC); + + ecs_fini(world); +} + +void Observer_3_terms_2_or_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB, .oper = EcsOr}, {TagC, .oper = EcsOr}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagC); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagC); + test_int(ctx.invoked, 1); + + ecs_remove_id(world, e, TagB); + test_int(ctx.invoked, 2); + + ecs_remove_id(world, e, TagA); + test_int(ctx.invoked, 2); + + ecs_fini(world); +} + +void Observer_2_terms_w_from_entity_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t x = ecs_new_id(world); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB, .oper = EcsOr, .args[0].entity = x}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_clear(world, e); + test_int(ctx.invoked, 0); + + ecs_add_id(world, x, TagB); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 1); + + ecs_fini(world); +} + +void Observer_2_terms_on_remove_on_clear() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_clear(world, e); + test_int(ctx.invoked, 2); + + ecs_fini(world); +} + +void Observer_2_terms_on_remove_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{TagA}, {TagB}}, + .events = {EcsOnRemove}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_delete(world, e); + test_int(ctx.invoked, 2); + + ecs_fini(world); +} + +void Observer_observer_w_self() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t self = ecs_new_id(world); + + Probe ctx = {0}; + ecs_entity_t system = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{Tag}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx, + .self = self + }); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + + test_int(ctx.count, 1); + test_assert(ctx.system == system); + test_assert(ctx.self == self); + + ecs_fini(world); +} + +void Observer_add_after_delete_observer() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t observer = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{.id = ecs_id(Position) }}, + .events = {EcsOnAdd}, + .callback = Dummy + }); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_delete(world, observer); + test_int(dummy_called, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + test_int(dummy_called, 0); + + ecs_fini(world); +} + +void Observer_remove_after_delete_observer() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t observer = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{.id = ecs_id(Position) }}, + .events = {EcsOnRemove}, + .callback = Dummy + }); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + test_int(dummy_called, 0); + + ecs_remove(world, e1, Position); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_delete(world, observer); + test_int(dummy_called, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + test_int(dummy_called, 0); + + ecs_remove(world, e2, Position); + test_int(dummy_called, 0); + + ecs_fini(world); +} + +static int ctx_value; +static +void ctx_free(void *ctx) { + test_assert(&ctx_value == ctx); + ctx_value ++; +} + +static int binding_ctx_value; +static +void binding_ctx_free(void *ctx) { + test_assert(&binding_ctx_value == ctx); + binding_ctx_value ++; +} + +void Observer_delete_observer_w_ctx() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{ Tag }}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx_value, + .ctx_free = ctx_free, + .binding_ctx = &binding_ctx_value, + .binding_ctx_free = binding_ctx_free + }); + test_assert(o != 0); + + test_assert(ecs_get_observer_ctx(world, o) == &ctx_value); + test_assert(ecs_get_observer_binding_ctx(world, o) == &binding_ctx_value); + + ecs_delete(world, o); + + test_int(ctx_value, 1); + test_int(binding_ctx_value, 1); + + ecs_fini(world); +} + +void Observer_filter_w_strings() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ + .filter.terms = {{.pred.name = "TagA"}, {.pred.name = "TagB"}}, + .events = {EcsOnAdd}, + .callback = Observer, + .ctx = &ctx + }); + test_assert(o != 0); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, TagB); + test_int(ctx.invoked, 0); + + ecs_add_id(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, o); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + test_int(ctx.c[0][1], TagB); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/OnDelete.c b/fggl/ecs2/flecs/test/api/src/OnDelete.c new file mode 100644 index 0000000000000000000000000000000000000000..cda1420979e4dd38d7425d903006b14f31f8a60e --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/OnDelete.c @@ -0,0 +1,1226 @@ +#include <api.h> + +void OnDelete_on_delete_id_default() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, c); + test_assert(ecs_has_id(world, e, c)); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_id_remove() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, EcsOnDelete, EcsRemove); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, c); + test_assert(ecs_has_id(world, e, c)); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_id_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, EcsOnDelete, EcsDelete); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, c); + test_assert(ecs_has_id(world, e, c)); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void OnDelete_on_delete_relation_default() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_relation_remove() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDelete, EcsRemove); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_relation_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDelete, EcsDelete); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, o)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void OnDelete_on_delete_object_default() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + ecs_delete(world, o); + + test_assert(ecs_is_alive(world, r)); + test_assert(!ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_object_remove() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsRemove); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + ecs_delete(world, o); + + test_assert(ecs_is_alive(world, r)); + test_assert(!ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_object_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsDelete); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + ecs_delete(world, o); + + test_assert(ecs_is_alive(world, r)); + test_assert(!ecs_is_alive(world, o)); + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void OnDelete_on_delete_id_throw() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, EcsOnDelete, EcsThrow); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, c); + test_assert(ecs_has_id(world, e, c)); + + test_expect_abort(); + ecs_delete(world, c); +} + +void OnDelete_on_delete_relation_throw() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDelete, EcsThrow); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + test_expect_abort(); + ecs_delete(world, r); +} + +void OnDelete_on_delete_object_throw() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsThrow); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o); + test_assert(ecs_has_pair(world, e, r, o)); + + test_expect_abort(); + ecs_delete(world, o); +} + +void OnDelete_on_delete_id_remove_no_instances() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, EcsOnDelete, EcsRemove); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + + ecs_fini(world); +} + +void OnDelete_on_delete_id_delete_no_instances() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, EcsOnDelete, EcsDelete); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + + ecs_fini(world); +} + +void OnDelete_on_delete_id_throw_no_instances() { + install_test_abort(); + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, EcsOnDelete, EcsThrow); + + test_expect_abort(); + ecs_delete(world, c); +} + +void OnDelete_on_delete_cyclic_id_default() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + + ecs_add_id(world, a, b); + ecs_add_id(world, b, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_id_remove() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsRemove); + + ecs_add_id(world, a, b); + ecs_add_id(world, b, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_id_remove_both() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsRemove); + ecs_add_pair(world, b, EcsOnDelete, EcsRemove); + + ecs_add_id(world, a, b); + ecs_add_id(world, b, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_int(ecs_vector_count(ecs_get_type(world, b)), 1); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_id_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsDelete); + + ecs_add_id(world, a, b); + ecs_add_id(world, b, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_id_delete_both() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsDelete); + ecs_add_pair(world, b, EcsOnDelete, EcsDelete); + + ecs_add_id(world, a, b); + ecs_add_id(world, b, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_relation_default() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + + ecs_add_id(world, a, b); + ecs_add_id(world, b, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_relation_remove() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t o_a = ecs_new_id(world); + ecs_entity_t o_b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsRemove); + + ecs_add_pair(world, a, b, o_b); + ecs_add_pair(world, b, a, o_a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_relation_remove_both() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t o_a = ecs_new_id(world); + ecs_entity_t o_b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsRemove); + ecs_add_pair(world, b, EcsOnDelete, EcsRemove); + + ecs_add_pair(world, a, b, o_b); + ecs_add_pair(world, b, a, o_a); + + ecs_delete(world, a); + + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_int(ecs_vector_count(ecs_get_type(world, b)), 1); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_relation_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t o_a = ecs_new_id(world); + ecs_entity_t o_b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsDelete); + + ecs_add_pair(world, a, b, o_b); + ecs_add_pair(world, b, a, o_a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_relation_delete_both() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t o_a = ecs_new_id(world); + ecs_entity_t o_b = ecs_new_id(world); + ecs_add_pair(world, a, EcsOnDelete, EcsDelete); + ecs_add_pair(world, b, EcsOnDelete, EcsDelete); + + ecs_add_pair(world, a, b, o_b); + ecs_add_pair(world, b, a, o_a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_object_default() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t r = ecs_new_id(world); + + ecs_add_pair(world, a, r, b); + ecs_add_pair(world, b, r, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_object_remove() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t r = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsRemove); + + ecs_add_pair(world, a, r, b); + ecs_add_pair(world, b, r, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(ecs_is_alive(world, b)); + test_assert(ecs_get_type(world, b) == NULL); + + ecs_delete(world, b); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_cyclic_object_delete() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t a = ecs_new_id(world); + ecs_entity_t b = ecs_new_id(world); + ecs_entity_t r = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsDelete); + + ecs_add_pair(world, a, r, b); + ecs_add_pair(world, b, r, a); + + ecs_delete(world, a); + test_assert(!ecs_is_alive(world, a)); + test_assert(!ecs_is_alive(world, b)); + + ecs_fini(world); +} + +void OnDelete_on_delete_remove_2_comps() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t c = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, c); + ecs_set(world, e, Position, {10, 20}); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void OnDelete_on_delete_remove_2_comps_to_existing_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t c = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, c); + ecs_set(world, e, Position, {10, 20}); + + ecs_entity_t e2 = ecs_new_id(world); + ecs_set(world, e2, Position, {20, 30}); + ecs_entity_t e3 = ecs_new_id(world); + ecs_set(world, e3, Position, {40, 50}); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_int(p->x, 20); + test_int(p->y, 30); + + p = ecs_get(world, e3, Position); + test_int(p->x, 40); + test_int(p->y, 50); + + ecs_fini(world); +} + +void OnDelete_on_delete_delete_recursive() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsDelete); + + ecs_entity_t p = ecs_new_id(world); + + ecs_entity_t c = ecs_new_id(world); + ecs_add_pair(world, c, r, p); + + ecs_entity_t gc = ecs_new_id(world); + ecs_add_pair(world, gc, r, c); + + ecs_delete(world, p); + + test_assert(!ecs_is_alive(world, p)); + test_assert(!ecs_is_alive(world, c)); + test_assert(!ecs_is_alive(world, gc)); + + ecs_fini(world); +} + +void OnDelete_on_delete_component_throw() { + install_test_abort(); + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, 0, Position, {10, 20}); + + test_expect_abort(); + ecs_delete(world, ecs_id(Position)); +} + +void OnDelete_on_delete_remove_2_relations() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o_1 = ecs_new_id(world); + ecs_entity_t o_2 = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, o_1); + ecs_add_pair(world, e, r, o_2); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, o_1)); + test_assert(ecs_is_alive(world, o_2)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_remove_object_w_2_relations() { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t r_1 = ecs_new_id(world); + ecs_entity_t r_2 = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r_1, o); + ecs_add_pair(world, e, r_2, o); + + ecs_delete(world, o); + + test_assert(!ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, r_1)); + test_assert(ecs_is_alive(world, r_2)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_remove_object_w_5_relations() { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t r_1 = ecs_new_id(world); + ecs_entity_t r_2 = ecs_new_id(world); + ecs_entity_t r_3 = ecs_new_id(world); + ecs_entity_t r_4 = ecs_new_id(world); + ecs_entity_t r_5 = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r_1, o); + ecs_add_pair(world, e, r_2, o); + ecs_add_pair(world, e, r_3, o); + ecs_add_pair(world, e, r_4, o); + ecs_add_pair(world, e, r_5, o); + + ecs_delete(world, o); + + test_assert(!ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_is_alive(world, r_1)); + test_assert(ecs_is_alive(world, r_2)); + test_assert(ecs_is_alive(world, r_3)); + test_assert(ecs_is_alive(world, r_4)); + test_assert(ecs_is_alive(world, r_5)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_remove_object_w_50_relations() { + ecs_world_t *world = ecs_mini(); + + const int NUM = 50; + + ecs_entity_t r[NUM]; + int i; + for (i = 0; i < NUM; i ++) { + r[i] = ecs_new_id(world); + } + + ecs_entity_t o = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + + for (i = 0; i < NUM; i ++) { + ecs_add_pair(world, e, r[i], o); + } + + ecs_delete(world, o); + + test_assert(!ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_on_delete_remove_object_w_50_relations_3_tables() { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + const int NUM = 50; + + ecs_entity_t r[NUM]; + int i; + for (i = 0; i < NUM; i ++) { + r[i] = ecs_new_id(world); + } + + ecs_entity_t o = ecs_new_id(world); + ecs_entity_t e_1 = ecs_new_w_id(world, TagA); + ecs_entity_t e_2 = ecs_new_w_id(world, TagB); + ecs_entity_t e_3 = ecs_new_w_id(world, TagC); + + for (i = 0; i < NUM; i ++) { + ecs_add_pair(world, e_1, r[i], o); + ecs_add_pair(world, e_2, r[i], o); + ecs_add_pair(world, e_3, r[i], o); + } + + ecs_delete(world, o); + + test_assert(!ecs_is_alive(world, o)); + + test_assert(ecs_is_alive(world, e_1)); + test_assert(ecs_is_alive(world, e_2)); + test_assert(ecs_is_alive(world, e_3)); + + test_assert(ecs_has_id(world, e_1, TagA)); + test_assert(ecs_has_id(world, e_2, TagB)); + test_assert(ecs_has_id(world, e_3, TagC)); + + ecs_fini(world); +} + +void OnDelete_remove_id_from_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t c = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add_id(world, e1, c); + + ecs_entity_t e2 = ecs_new_id(world); + ecs_add_id(world, e2, c); + ecs_add_id(world, e2, Tag); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(ecs_get_type(world, e1) == NULL); + test_int(ecs_vector_count(ecs_get_type(world, e2)), 1); + test_assert(ecs_has_id(world, e2, Tag)); + + ecs_fini(world); +} + +void OnDelete_remove_relation_from_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add_pair(world, e1, r, o); + + ecs_entity_t e2 = ecs_new_id(world); + ecs_add_pair(world, e1, r, o); + ecs_add_id(world, e2, Tag); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(ecs_get_type(world, e1) == NULL); + test_int(ecs_vector_count(ecs_get_type(world, e2)), 1); + test_assert(ecs_has_id(world, e2, Tag)); + + ecs_fini(world); +} + +void OnDelete_remove_object_from_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add_pair(world, e1, r, o); + + ecs_entity_t e2 = ecs_new_id(world); + ecs_add_pair(world, e1, r, o); + ecs_add_id(world, e2, Tag); + + ecs_delete(world, o); + + test_assert(!ecs_is_alive(world, o)); + test_assert(ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(ecs_get_type(world, e1) == NULL); + test_int(ecs_vector_count(ecs_get_type(world, e2)), 1); + test_assert(ecs_has_id(world, e2, Tag)); + + ecs_fini(world); +} + +void OnDelete_remove_id_and_relation() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, r); + ecs_add_pair(world, e, r, o); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + + ecs_fini(world); +} + +void OnDelete_remove_id_and_relation_from_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t o = ecs_new_id(world); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_add_pair(world, e1, r, o); + ecs_add_id(world, e1, r); + + ecs_entity_t e2 = ecs_new_id(world); + ecs_add_pair(world, e1, r, o); + ecs_add_id(world, e2, r); + ecs_add_id(world, e2, Tag); + + ecs_delete(world, r); + + test_assert(!ecs_is_alive(world, r)); + test_assert(ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(ecs_get_type(world, e1) == NULL); + test_int(ecs_vector_count(ecs_get_type(world, e2)), 1); + test_assert(ecs_has_id(world, e2, Tag)); + + ecs_fini(world); +} + +void OnDelete_stresstest_many_objects() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int i, ri; + + ecs_world_stats_t s = {0}; + ecs_get_world_stats(world, &s); + + float table_count = s.table_count.avg[s.t]; + + /* Precreate relations so we get different relationship ids */ + ecs_entity_t relations[100]; + for (i = 0; i < 100; i ++) { + relations[i] = ecs_new_id(world); + } + + for (ri = 0; ri < 100; ri ++) { + for (i = 0; i < 100; i ++) { + ecs_entity_t o = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + ecs_add_pair(world, e, relations[ri], o); + } + + ecs_delete(world, relations[ri]); + } + + ecs_get_world_stats(world, &s); + + test_int(s.table_count.avg[s.t] - table_count, 1); + + ecs_fini(world); +} + +void OnDelete_stresstest_many_relations() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int i, oi; + + ecs_world_stats_t s = {0}; + ecs_get_world_stats(world, &s); + + float table_count = s.table_count.avg[s.t]; + + /* Precreate objects so we get different relationship ids */ + ecs_entity_t objects[100]; + for (i = 0; i < 100; i ++) { + objects[i] = ecs_new_id(world); + } + + for (oi = 0; oi < 100; oi ++) { + for (i = 0; i < 100; i ++) { + ecs_entity_t r = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + ecs_add_pair(world, e, r, objects[oi]); + } + + ecs_delete(world, objects[oi]); + } + + ecs_get_world_stats(world, &s); + + test_int(s.table_count.avg[s.t] - table_count, 1); + + ecs_fini(world); +} + +void OnDelete_stresstest_many_objects_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int i, ri; + + /* Precreate relations so we get different relationship ids */ + ecs_entity_t relations[100]; + for (i = 0; i < 100; i ++) { + relations[i] = ecs_new_id(world); + ecs_add_pair(world, relations[i], EcsOnDelete, EcsDelete); + } + + ecs_world_stats_t s = {0}; + ecs_get_world_stats(world, &s); + float table_count = s.table_count.avg[s.t]; + + for (ri = 0; ri < 100; ri ++) { + for (i = 0; i < 100; i ++) { + ecs_entity_t o = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, relations[ri], o); + ecs_add_id(world, e, Tag); + } + + ecs_delete(world, relations[ri]); + } + + ecs_get_world_stats(world, &s); + test_int(s.table_count.avg[s.t] - table_count, 0); + + ecs_fini(world); +} + +void OnDelete_stresstest_many_relations_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int i, oi; + + /* Precreate objects so we get different relationship ids */ + ecs_entity_t objects[100]; + for (i = 0; i < 100; i ++) { + objects[i] = ecs_new_id(world); + } + + ecs_world_stats_t s = {0}; + ecs_get_world_stats(world, &s); + float table_count = s.table_count.avg[s.t]; + + for (oi = 0; oi < 100; oi ++) { + for (i = 0; i < 100; i ++) { + ecs_entity_t r = ecs_new_id(world); + ecs_add_pair(world, r, EcsOnDeleteObject, EcsDelete); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, r, objects[oi]); + ecs_add_id(world, e, Tag); + } + + ecs_delete(world, objects[oi]); + } + + ecs_get_world_stats(world, &s); + + /* 1 table for the relations */ + test_int(s.table_count.avg[s.t] - table_count, 1); + + ecs_fini(world); +} + +static ecs_entity_t trigger_entity; +static int trigger_count; + +static +void dummy_on_remove(ecs_iter_t *it) { + test_int(it->count, 1); + trigger_entity = it->entities[0]; + + trigger_count ++; + test_int(trigger_count, 1); +} + +void OnDelete_on_delete_empty_table_w_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + ecs_add_id(world, e1, e2); + ecs_add_id(world, e1, Tag); + + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .events = {EcsOnRemove}, + .term.id = e2, + .callback = dummy_on_remove + }); + + ecs_delete(world, e1); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(trigger_entity != 0); + test_assert(trigger_entity == e1); + test_int(trigger_count, 1); + + // Will delete table of e1, which is now empty. Should not call trigger + ecs_delete(world, e2); + + test_int(trigger_count, 1); + + ecs_fini(world); +} + +typedef struct { + ecs_entity_t other; +} Entity; + +static +void delete_on_remove(ecs_iter_t *it) { + test_int(it->count, 1); + Entity *comp = ecs_term(it, Entity, 1); + test_assert(comp != NULL); + test_assert(comp->other != 0); + ecs_delete(it->world, comp->other); + trigger_count ++; +} + +static +void delete_self_on_remove(ecs_iter_t *it) { + test_int(it->count, 1); + ecs_delete(it->world, it->self); + trigger_count ++; +} + +void OnDelete_delete_table_in_on_remove_during_fini() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + /* Setup scenario so that the order in which tables are deleted doesn't + * matter for reproducing the issue */ + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + /* Add entities to each other so that deleting one will delete the table of + * the other. */ + ecs_add_id(world, e1, e2); + ecs_add_id(world, e2, e1); + + /* Make sure entities are in different tables */ + ecs_add_id(world, e1, Tag); + + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .events = {EcsOnRemove}, + .term.id = e1, + .callback = delete_self_on_remove, + .self = e2 + }); + + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .events = {EcsOnRemove}, + .term.id = e2, + .callback = delete_self_on_remove, + .self = e1 + }); + + ecs_fini(world); + + /* Trigger must have been called for both entities */ + test_int(trigger_count, 2); +} + +void OnDelete_delete_other_in_on_remove_during_fini() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Entity); + ECS_TAG(world, Tag); + + /* Setup scenario so that the order in which tables are deleted doesn't + * matter for reproducing the issue */ + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + + /* Removing Entity from one will delete the other */ + ecs_set(world, e1, Entity, {e2}); + ecs_set(world, e2, Entity, {e1}); + + /* Make sure entities are in different tables */ + ecs_add_id(world, e1, Tag); + + ecs_trigger_init(world, &(ecs_trigger_desc_t) { + .events = {EcsOnRemove}, + .term.id = ecs_id(Entity), + .callback = delete_on_remove + }); + + ecs_fini(world); + + /* Trigger must have been called for both entities */ + test_int(trigger_count, 2); +} + +void OnDelete_on_delete_remove_id_w_role() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t c = ecs_new_id(world); + + ecs_entity_t e = ecs_new_id(world); + + ecs_add_id(world, e, ECS_DISABLED | c); + test_assert(ecs_has_id(world, e, ECS_DISABLED | c)); + + ecs_delete(world, c); + + test_assert(!ecs_is_alive(world, c)); + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_get_type(world, e) == NULL); + test_assert(!ecs_has_id(world, e, ECS_DISABLED | c)); + + ecs_fini(world); +} + +void OnDelete_on_delete_merge_pair_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e_1 = ecs_new_id(world); + ecs_entity_t e_2 = ecs_new_id(world); + ecs_entity_t obj = ecs_new_id(world); + + ecs_add_id(world, e_1, e_2); + ecs_set_pair(world, e_1, Position, obj, {10, 20}); + ecs_delete(world, e_2); + + const Position *ptr = ecs_get_pair(world, e_1, Position, obj); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Pairs.c b/fggl/ecs2/flecs/test/api/src/Pairs.c new file mode 100644 index 0000000000000000000000000000000000000000..ba17405b5d3cea6ff1ef4f173709b8010538bb63 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Pairs.c @@ -0,0 +1,1986 @@ +#include <api.h> + +typedef struct Rel { + float value; +} Rel; + +typedef struct RelA { + float value; +} RelA; + +typedef struct RelB { + float value; +} RelB; + +typedef struct Obj { + float value; +} Obj; + +void ProcessPairs(ecs_iter_t *it) { + Rel *tr = ecs_term(it, Rel, 1); + + probe_system(it); + + if (tr) { + int32_t i; + for (i = 0; i < it->count; i ++) { + tr[i].value ++; + } + } +} + +void Pairs_type_w_one_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_SYSTEM(world, ProcessPairs, EcsOnUpdate, (Rel, *)); + + /* Ensure that pair is matched against different components */ + ecs_entity_t e1 = ecs_set_pair(world, 0, Rel, ecs_id(Position), { + .value = 10 + }); + + test_assert(e1 != 0); + test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position))); + + ecs_entity_t e2 = ecs_set_pair(world, 0, Rel, ecs_id(Velocity), { + .value = 20 + }); + test_assert(e2 != 0); + test_assert( ecs_has_pair(world, e2, ecs_id(Rel), ecs_id(Velocity))); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 0); + + const Rel* tr = ecs_get_pair(world, e1, Rel, ecs_id(Position)); + test_assert(tr != NULL); + test_int(tr->value, 11); + + tr = ecs_get_pair(world, e2, Rel, ecs_id(Velocity)); + test_assert(tr != NULL); + test_int(tr->value, 21); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, ProcessPairs); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Position)); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Velocity)); + + test_int(ctx.s[0][0], 0); + test_int(ctx.s[1][0], 0); + + ecs_fini(world); +} + +void Pairs_type_w_two_pairs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_SYSTEM(world, ProcessPairs, EcsOnUpdate, (Rel, *)); + + /* Ensure that pair is matched against different components on same entity */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_set_pair(world, e1, Rel, ecs_id(Position), { + .value = 10 + }); + test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position))); + + ecs_set_pair(world, e1, Rel, ecs_id(Velocity), { + .value = 20 + }); + test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Velocity))); + + ecs_entity_t e2 = ecs_new(world, 0); + ecs_set_pair(world, e2, Rel, ecs_id(Position), { + .value = 30 + }); + test_assert( ecs_has_pair(world, e2, ecs_id(Rel), ecs_id(Position))); + + ecs_set_pair(world, e2, Rel, ecs_id(Velocity), { + .value = 40 + }); + test_assert( ecs_has_pair(world, e2, ecs_id(Rel), ecs_id(Position))); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 0); + + const Rel* tr = ecs_get_pair(world, e1, Rel, ecs_id(Position)); + test_assert(tr != NULL); + test_int(tr->value, 11); + + tr = ecs_get_pair(world, e1, Rel, ecs_id(Velocity)); + test_assert(tr != NULL); + test_int(tr->value, 21); + + tr = ecs_get_pair(world, e2, Rel, ecs_id(Position)); + test_assert(tr != NULL); + test_int(tr->value, 31); + + tr = ecs_get_pair(world, e2, Rel, ecs_id(Velocity)); + test_assert(tr != NULL); + test_int(tr->value, 41); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 2); + test_int(ctx.system, ProcessPairs); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e1); + test_int(ctx.e[3], e2); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Velocity)); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Position)); + + test_int(ctx.s[0][0], 0); + test_int(ctx.s[1][0], 0); + test_int(ctx.s[2][0], 0); + test_int(ctx.s[3][0], 0); + + ecs_fini(world); +} + +void Pairs_add_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_SYSTEM(world, ProcessPairs, EcsOnUpdate, (Rel, *)); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_add_pair(world, e1, ecs_id(Rel), ecs_id(Position)); + test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position))); + + ecs_fini(world); +} + +void Pairs_remove_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_SYSTEM(world, ProcessPairs, EcsOnUpdate, (Rel, *)); + + ecs_entity_t e1 = ecs_set_pair(world, 0, Rel, ecs_id(Position), { + .value = 10 + }); + test_assert(e1 != 0); + test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position))); + + ecs_remove_pair(world, e1, ecs_id(Rel), ecs_id(Position)); + test_assert( !ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position))); + + ecs_fini(world); +} + +void ProcessPairTags(ecs_iter_t *it) { + probe_system(it); +} + +void Pairs_add_tag_pair_for_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag1); + ECS_TAG(world, Tag2); + ECS_TAG(world, Rel); + + ECS_SYSTEM(world, ProcessPairTags, EcsOnUpdate, (Rel, *)); + + /* Ensure that pair is matched against different components */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add_pair(world, e1, Rel, Tag1); + test_assert(e1 != 0); + test_assert( ecs_has_pair(world, e1, Rel, Tag1)); + + ecs_entity_t e2 = ecs_new(world, 0); + ecs_add_pair(world, e2, Rel, Tag2); + test_assert(e2 != 0); + test_assert( ecs_has_pair(world, e2, Rel, Tag2)); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 0); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, ProcessPairTags); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + + ecs_entity_t c = ctx.c[0][0]; + + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, Tag1); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, Tag2); + + test_int(ctx.s[0][0], 0); + test_int(ctx.s[1][0], 0); + + ecs_fini(world); +} + +void ProcessValuePairs(ecs_iter_t *it) { + /* Strictly speaking this can be either Position or Velocity, but they have + * the same layout. */ + Position *p = ecs_term(it, Position, 1); + + probe_system(it); + + if (p) { + int32_t i; + for (i = 0; i < it->count; i ++) { + p[i].x += 10; + p[i].y += 20; + } + } +} + +void Pairs_add_tag_pair_for_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, Rel); + + ECS_SYSTEM(world, ProcessValuePairs, EcsOnUpdate, (Rel, *)); + + ecs_entity_t e1 = ecs_set_pair_object(world, 0, Rel, Position, { + .x = 1, + .y = 2 + }); + test_assert( ecs_has_pair(world, e1, Rel, ecs_id(Position))); + + ecs_entity_t e2 = ecs_set_pair_object(world, 0, Rel, Velocity, { + .x = 3, + .y = 4 + }); + test_assert( ecs_has_pair(world, e2, Rel, ecs_id(Velocity))); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 0); + + const Position* tr_p = ecs_get_pair_object(world, e1, Rel, Position); + test_assert(tr_p != NULL); + test_int(tr_p->x, 11); + test_int(tr_p->y, 22); + + const Velocity* tr_v = ecs_get_pair_object(world, e2, Rel, Velocity); + test_assert(tr_v != NULL); + test_int(tr_v->x, 13); + test_int(tr_v->y, 24); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, ProcessValuePairs); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, ecs_id(Position)); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, ecs_id(Velocity)); + + test_int(ctx.s[0][0], 0); + test_int(ctx.s[1][0], 0); + + ecs_fini(world); +} + +void ProcessTwoPairs(ecs_iter_t *it) { + RelA *tr_a = ecs_term(it, RelA, 1); + RelB *tr_b = ecs_term(it, RelB, 2); + + probe_system(it); + + int32_t i; + for (i = 0; i < it->count; i ++) { + tr_a[i].value ++; + tr_b[i].value ++; + } +} + +void Pairs_query_2_pairs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, RelA); + ECS_COMPONENT(world, RelB); + + ECS_SYSTEM(world, ProcessTwoPairs, EcsOnUpdate, (RelA, *), (RelB, *)); + + /* Create entity with both RelA and RelB */ + ecs_entity_t e1 = ecs_set_pair(world, 0, RelA, ecs_id(Position), { + .value = 1 + }); + + ecs_set_pair(world, e1, RelB, ecs_id(Position), { + .value = 2 + }); + + test_assert( ecs_has_pair(world, e1, ecs_id(RelA), ecs_id(Position))); + test_assert( ecs_has_pair(world, e1, ecs_id(RelB), ecs_id(Position))); + + /* Create entity with only RelA. Should not be matched with system */ + ecs_entity_t e2 = ecs_set_pair(world, 0, RelA, ecs_id(Position), { + .value = 3 + }); + test_assert( ecs_has_pair(world, e2, ecs_id(RelA), ecs_id(Position))); + + /* Run system */ + Probe ctx = {0}; + ecs_set_context(world, &ctx); + ecs_progress(world, 0); + + const RelA *tr_a = ecs_get_pair(world, e1, RelA, ecs_id(Position)); + test_int(tr_a->value, 2); + + const RelB *tr_b = ecs_get_pair(world, e1, RelB, ecs_id(Position)); + test_int(tr_b->value, 3); + + tr_a = ecs_get_pair(world, e2, RelA, ecs_id(Position)); + test_int(tr_a->value, 3); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, ProcessTwoPairs); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(RelA)); + test_int(lo, ecs_id(Position)); + + c = ctx.c[0][1]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(RelB)); + test_int(lo, ecs_id(Position)); + + ecs_fini(world); +} + +void Pairs_query_2_pairs_2_instances_per_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, RelA); + ECS_COMPONENT(world, RelB); + + ECS_SYSTEM(world, ProcessTwoPairs, EcsOnUpdate, (RelA, *), (RelB, *)); + + /* Create entity with both RelA and RelB, applied to two components*/ + ecs_entity_t e1 = ecs_set_pair(world, 0, RelA, ecs_id(Position), { + .value = 1 + }); + + ecs_set_pair(world, e1, RelB, ecs_id(Position), { + .value = 2 + }); + + ecs_set_pair(world, e1, RelA, ecs_id(Velocity), { + .value = 3 + }); + + ecs_set_pair(world, e1, RelB, ecs_id(Velocity), { + .value = 4 + }); + + test_assert( ecs_has_pair(world, e1, ecs_id(RelA), ecs_id(Position))); + test_assert( ecs_has_pair(world, e1, ecs_id(RelB), ecs_id(Position))); + test_assert( ecs_has_pair(world, e1, ecs_id(RelA), ecs_id(Velocity))); + test_assert( ecs_has_pair(world, e1, ecs_id(RelB), ecs_id(Velocity))); + + /* Run system */ + Probe ctx = {0}; + ecs_set_context(world, &ctx); + ecs_progress(world, 0); + + const RelA *tr_a = ecs_get_pair(world, e1, RelA, ecs_id(Position)); + test_int(tr_a->value, 2); + + tr_a = ecs_get_pair(world, e1, RelA, ecs_id(Velocity)); + test_int(tr_a->value, 4); + + const RelB *tr_b = ecs_get_pair(world, e1, RelB, ecs_id(Position)); + test_int(tr_b->value, 3); + + tr_b = ecs_get_pair(world, e1, RelB, ecs_id(Velocity)); + test_int(tr_b->value, 5); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, ProcessTwoPairs); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e1); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(RelA)); + test_int(lo, ecs_id(Position)); + + c = ctx.c[0][1]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(RelB)); + test_int(lo, ecs_id(Position)); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(RelA)); + test_int(lo, ecs_id(Velocity)); + + c = ctx.c[1][1]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(RelB)); + test_int(lo, ecs_id(Velocity)); + + ecs_fini(world); +} + +void Pairs_override_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rel); + + ecs_entity_t base = ecs_new(world, 0); + ecs_set_pair(world, base, Rel, ecs_id(Position), {.value = 10}); + + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + test_assert(ecs_has_pair(world, instance, ecs_id(Rel), ecs_id(Position))); + + const Rel *t = ecs_get_pair(world, instance, Rel, ecs_id(Position)); + test_assert(t != NULL); + test_int(t->value, 10); + + const Rel *t_2 = ecs_get_pair(world, base, Rel, ecs_id(Position)); + test_assert(t_2 != NULL); + test_assert(t == t_2); + + ecs_add_pair(world, instance, ecs_id(Rel), ecs_id(Position)); + t = ecs_get_pair(world, instance, Rel, ecs_id(Position)); + test_assert(t != NULL); + test_int(t->value, 10); + test_assert(t != t_2); + + ecs_fini(world); +} + +void Pairs_override_tag_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ecs_entity_t base = ecs_new(world, 0); + ecs_set_pair_object(world, base, Rel, Position, {.x = 10, .y = 20}); + + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + test_assert(ecs_has_pair(world, instance, Rel, ecs_id(Position))); + + const Position *t = ecs_get_pair_object(world, instance, Rel, Position); + test_assert(t != NULL); + test_int(t->x, 10); + test_int(t->y, 20); + + const Position *t_2 = ecs_get_pair_object(world, base, Rel, Position); + test_assert(t_2 != NULL); + test_assert(t == t_2); + + ecs_add_pair(world, instance, Rel, ecs_id(Position)); + t = ecs_get_pair_object(world, instance, Rel, Position); + test_assert(t != NULL); + test_int(t->x, 10); + test_int(t->y, 20); + test_assert(t != t_2); + + ecs_fini(world); +} + +static +void PairTrigger(ecs_iter_t *it) { + probe_system(it); +} + +void Pairs_on_add_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_TRIGGER(world, PairTrigger, EcsOnAdd, (Rel, *)); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position)); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Position)); + + ctx = (Probe){ 0 }; + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Velocity)); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + c = ctx.c[0][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Velocity)); + + ecs_fini(world); +} + +void Pairs_on_add_pair_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ECS_TRIGGER(world, PairTrigger, EcsOnAdd, (Rel, Position)); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, Rel, ecs_id(Position)); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, ecs_id(Position)); + + ecs_fini(world); +} + +void Pairs_on_remove_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_TRIGGER(world, PairTrigger, EcsOnRemove, (Rel, *)); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position)); + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Velocity)); + test_int(ctx.count, 0); + + ecs_remove_pair(world, e, ecs_id(Rel), ecs_id(Position)); + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Position)); + + ctx = (Probe){ 0 }; + ecs_remove_pair(world, e, ecs_id(Rel), ecs_id(Velocity)); + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + c = ctx.c[0][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Velocity)); + + ecs_fini(world); +} + +void Pairs_on_remove_pair_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ECS_TRIGGER(world, PairTrigger, EcsOnRemove, (Rel, Position)); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, Rel, ecs_id(Position)); + test_int(ctx.count, 0); + + ecs_remove_pair(world, e, Rel, ecs_id(Position)); + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, ecs_id(Position)); + + ecs_fini(world); +} + +void Pairs_on_remove_pair_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rel); + + ECS_TRIGGER(world, PairTrigger, EcsOnRemove, (Rel, *)); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position)); + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Velocity)); + test_int(ctx.count, 0); + + ecs_delete(world, e); + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, PairTrigger); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Position)); + + test_int(ctx.e[1], e); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, ecs_id(Rel)); + test_int(lo, ecs_id(Velocity)); + + ecs_fini(world); +} + +static +void PairTriggerPosition(ecs_iter_t *it) { + PairTrigger(it); +} + +static +void PairTriggerVelocity(ecs_iter_t *it) { + PairTrigger(it); +} + +void Pairs_on_remove_pair_tag_on_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, Rel); + + ECS_TRIGGER(world, PairTriggerPosition, EcsOnRemove, (Rel, Position)); + ECS_TRIGGER(world, PairTriggerVelocity, EcsOnRemove, (Rel, Velocity)); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, Rel, ecs_id(Position)); + ecs_add_pair(world, e, Rel, ecs_id(Velocity)); + test_int(ctx.count, 0); + + ecs_delete(world, e); + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, PairTriggerVelocity); + test_int(ctx.column_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + + ecs_entity_t c = ctx.c[0][0]; + ecs_entity_t hi = ECS_PAIR_RELATION(c); + ecs_entity_t lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, ecs_id(Position)); + + test_int(ctx.e[1], e); + + c = ctx.c[1][0]; + hi = ECS_PAIR_RELATION(c); + lo = ECS_PAIR_OBJECT(c); + test_int(hi, Rel); + test_int(lo, ecs_id(Velocity)); + + ecs_fini(world); +} + +void Pairs_pair_w_component_query() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rel); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position)); + + ecs_query_t *q = ecs_query_new(world, "(Rel, Position)"); + + int32_t count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Rel *t = ecs_term(&it, Rel, 1); + test_assert(t != NULL); + + int i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e); + } + count += it.count; + } + + test_assert(count == 1); + + ecs_fini(world); +} + +void Pairs_query_pair_or_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add_pair(world, e1, Rel, ecs_id(Position)); + ecs_entity_t e2 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "(Rel, Position) || Position"); + + int32_t count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *t = ecs_term(&it, Position, 1); + test_assert(t != NULL); + + int i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e1 || it.entities[i] == e2); + } + count += it.count; + } + + test_assert(count == 2); + + ecs_fini(world); +} + +void Pairs_query_pair_or_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, RelA); + ECS_TAG(world, RelB); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add_pair(world, e1, RelA, ecs_id(Position)); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add_pair(world, e2, RelB, ecs_id(Position)); + + ecs_query_t *q = ecs_query_new(world, "(RelA, Position) || (RelB, Position)"); + + int32_t count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *t = ecs_term(&it, Position, 1); + test_assert(t != NULL); + + int i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e1 || it.entities[i] == e2); + } + count += it.count; + } + + test_int(count, 2); + + ecs_fini(world); +} + +void Pairs_query_not_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add_pair(world, e2, Rel, ecs_id(Position)); + + ecs_query_t *q = ecs_query_new(world, "!(Rel, Position), Position"); + + int32_t count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *t = ecs_term(&it, Position, 1); + test_assert(t == NULL); + Position *p = ecs_term(&it, Position, 2); + test_assert(p != NULL); + + int i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e1); + } + count += it.count; + } + + test_assert(count == 1); + + ecs_fini(world); +} + +void Pairs_get_typeid_w_recycled_rel() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t o = ecs_new_id(world); + test_assert(o != 0); + + ecs_entity_t dummy = ecs_new_id(world); + ecs_delete(world, dummy); // force recycle + + // don't use ECS_COMPONENT, because it will try to get low ids first + ecs_entity_t pos = ecs_set(world, 0, EcsComponent, {4}); + test_assert(pos != 0); + test_assert((uint32_t)pos != pos); // ensure recycled + + ecs_id_t pair = ecs_pair(pos, o); + ecs_entity_t tid = ecs_get_typeid(world, pair); + test_assert(tid != 0); + test_assert(tid == pos); + + ecs_fini(world); +} + +void Pairs_get_typeid_w_recycled_obj() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_new_id(world); + test_assert(r != 0); + + ecs_entity_t dummy = ecs_new_id(world); + ecs_delete(world, dummy); // force recycle + + // don't use ECS_COMPONENT, because it will try to get low ids first + ecs_entity_t pos = ecs_set(world, 0, EcsComponent, {4}); + test_assert(pos != 0); + test_assert((uint32_t)pos != pos); // ensure recycled + + ecs_id_t pair = ecs_pair(r, pos); + ecs_entity_t tid = ecs_get_typeid(world, pair); + test_assert(tid != 0); + test_assert(tid == pos); + + ecs_fini(world); +} + +void Pairs_id_str_w_recycled_rel() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t o = ecs_set_name(world, 0, "o"); + test_assert(o != 0); + + ecs_entity_t dummy = ecs_new_id(world); + ecs_delete(world, dummy); // force recycle + + ecs_entity_t r = ecs_set_name(world, 0, "r"); + test_assert(r != 0); + test_assert((uint32_t)r != r); // ensure recycled + + ecs_id_t pair = ecs_pair(r, o); + char buff[64]; + ecs_id_str(world, pair, buff, sizeof(buff)); + + test_str(buff, "(r,o)"); + + ecs_fini(world); +} + +void Pairs_id_str_w_recycled_obj() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_set_name(world, 0, "r"); + test_assert(r != 0); + + ecs_entity_t dummy = ecs_new_id(world); + ecs_delete(world, dummy); // force recycle + + ecs_entity_t o = ecs_set_name(world, 0, "o"); + test_assert(o != 0); + test_assert((uint32_t)o != o); // ensure recycled + + ecs_id_t pair = ecs_pair(r, o); + char buff[64]; + ecs_id_str(world, pair, buff, sizeof(buff)); + + test_str(buff, "(r,o)"); + + ecs_fini(world); +} + +void Pairs_set_object_w_zero_sized_rel_comp() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t r = ecs_set(world, 0, EcsComponent, {0}); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set_pair_object(world, 0, r, Position, {10, 20}); + + const Position *p = ecs_get_pair_object(world, e, r, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Pairs_dsl_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "(Rel, Obj)" + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_dsl_pair_w_pred_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "(*, Obj)" + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_dsl_pair_w_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "(Rel_2, *)" + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj_2)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_dsl_pair_w_both_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "(*, *), Tag" // add Tag or we'd match builtin entities + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj), Tag} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2), Tag} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj), Tag} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2), Tag} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_2)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj_2)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_dsl_pair_w_explicit_subj_this() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "Rel(This, Obj)" + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_dsl_pair_w_explicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ECS_TAG(world, Tag); + + ecs_entity_t Subj = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "Subj", .add = {ecs_pair(Rel, Obj)} }); + test_assert(Subj != 0); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.expr = "Rel(Subj, Obj), Tag" + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2), Tag} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), Subj); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_api_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_pair(Rel, Obj)}} + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_api_pair_w_pred_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_pair(EcsWildcard, Obj)}} + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_api_pair_w_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_pair(Rel_2, EcsWildcard)}} + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj_2)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_api_pair_w_both_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + // add Tag or we'd match builtin entities + .filter.terms = {{ecs_pair(EcsWildcard, EcsWildcard)}, {Tag}} + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj), Tag} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2), Tag} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj), Tag} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2), Tag} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e2); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj_2)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj)); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel_2, Obj_2)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_api_pair_w_explicit_subj_this() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_pair(Rel, Obj), .args[0].entity = EcsThis}} + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2)} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_api_pair_w_explicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Rel_2); + ECS_TAG(world, Obj); + ECS_TAG(world, Obj_2); + + ECS_TAG(world, Tag); + + ecs_entity_t Subj = ecs_entity_init(world, &(ecs_entity_desc_t){ + .name = "Subj", .add = {ecs_pair(Rel, Obj)} }); + test_assert(Subj != 0); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_pair(Rel, Obj), .args[0].entity = Subj}, {Tag}} + }); + + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} }); + test_assert(e1 != 0); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj_2)} }); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj)} }); + test_assert(e3 != 0); + + ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel_2, Obj_2), Tag} }); + test_assert(e4 != 0); + + ecs_iter_t it = ecs_query_iter(q); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e4); + test_int(ecs_term_id(&it, 1), ecs_pair(Rel, Obj)); + test_int(ecs_term_source(&it, 1), Subj); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +void Pairs_typeid_from_tag() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t tag = ecs_new_id(world); + test_assert(tag != 0); + + ecs_entity_t id = ecs_get_typeid(world, tag); + test_assert(id != 0); + test_assert(id == tag); + + ecs_fini(world); +} + +void Pairs_typeid_from_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Rel); + ecs_id_t rel_id = ecs_id(Rel); + test_assert(rel_id != 0); + + ecs_entity_t id = ecs_get_typeid(world, rel_id); + test_assert(id != 0); + test_assert(id == rel_id); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t rel_id = ecs_new_id(world); + test_assert(rel_id != 0); + + ecs_entity_t obj_id = ecs_new_id(world); + test_assert(obj_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair_w_rel_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Rel); + ecs_id_t rel_id = ecs_id(Rel); + test_assert(rel_id != 0); + + ecs_entity_t obj_id = ecs_new_id(world); + test_assert(obj_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id != 0); + test_assert(id == rel_id); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair_w_obj_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Obj); + ecs_id_t obj_id = ecs_id(Obj); + test_assert(obj_id != 0); + + ecs_entity_t rel_id = ecs_new_id(world); + test_assert(rel_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id != 0); + test_assert(id == obj_id); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair_w_rel_obj_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Rel); + ecs_id_t rel_id = ecs_id(Rel); + test_assert(rel_id != 0); + + ECS_COMPONENT(world, Obj); + ecs_id_t obj_id = ecs_id(Obj); + test_assert(obj_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id != 0); + test_assert(id == rel_id); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair_w_rel_0_obj_type() { + ecs_world_t *world = ecs_init(); + + ecs_id_t rel_id = ecs_set(world, 0, EcsComponent, {0}); + test_assert(rel_id != 0); + + ECS_COMPONENT(world, Obj); + ecs_id_t obj_id = ecs_id(Obj); + test_assert(obj_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id != 0); + test_assert(id == obj_id); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair_w_rel_obj_0_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Rel); + ecs_id_t rel_id = ecs_id(Rel); + test_assert(rel_id != 0); + + ecs_id_t obj_id = ecs_set(world, 0, EcsComponent, {0}); + test_assert(obj_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id != 0); + test_assert(id == rel_id); + + ecs_fini(world); +} + +void Pairs_typeid_from_pair_w_rel_0_obj_0_type() { + ecs_world_t *world = ecs_init(); + + ecs_id_t rel_id = ecs_set(world, 0, EcsComponent, {0}); + test_assert(rel_id != 0); + + ecs_id_t obj_id = ecs_set(world, 0, EcsComponent, {0}); + test_assert(obj_id != 0); + + ecs_id_t pair_id = ecs_pair(rel_id, obj_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_tag_pair_w_rel_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Obj); + + ecs_add_id(world, ecs_id(Position), EcsTag); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(ecs_id(Position), Obj); + + ecs_add_id(world, e, pair_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_tag_pair_w_obj_comp() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_COMPONENT(world, Position); + + ecs_add_id(world, Rel, EcsTag); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(Rel, ecs_id(Position)); + + ecs_add_id(world, e, pair_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_tag_pair_w_rel_obj_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_add_id(world, ecs_id(Position), EcsTag); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(ecs_id(Position), ecs_id(Velocity)); + + ecs_add_id(world, e, pair_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_get_tag_pair_w_rel_comp() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Obj); + + ecs_add_id(world, ecs_id(Position), EcsTag); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(ecs_id(Position), Obj); + + ecs_add_id(world, e, pair_id); + + test_expect_abort(); + ecs_get_id(world, e, pair_id); +} + +void Pairs_get_tag_pair_w_obj_comp() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_add_id(world, ecs_id(Position), EcsTag); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(ecs_id(Position), ecs_id(Velocity)); + + ecs_add_id(world, e, pair_id); + + test_expect_abort(); + ecs_get_id(world, e, pair_id); +} + +void Pairs_get_tag_pair_w_rel_obj_comp() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_add_id(world, ecs_id(Position), EcsTag); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(ecs_id(Position), ecs_id(Velocity)); + + ecs_add_id(world, e, pair_id); + + test_expect_abort(); + ecs_get_id(world, e, pair_id); +} + +void Pairs_tag_pair_w_childof_w_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(EcsChildOf, ecs_id(Position)); + + ecs_add_id(world, e, pair_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_tag_pair_w_isa_w_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + ecs_id_t pair_id = ecs_pair(EcsIsA, ecs_id(Position)); + + ecs_add_id(world, e, pair_id); + + ecs_entity_t id = ecs_get_typeid(world, pair_id); + test_assert(id == 0); + + ecs_fini(world); +} + +void Pairs_get_1_object() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + ecs_entity_t rel = ecs_new_id(world); + ecs_entity_t obj = ecs_new_id(world); + + ecs_add_pair(world, e, rel, obj); + test_assert(ecs_has_pair(world, e, rel, obj)); + test_assert(ecs_get_object(world, e, rel, 0) == obj); + + ecs_fini(world); +} + +void Pairs_get_1_object_not_found() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + ecs_entity_t rel = ecs_new_id(world); + + test_assert(ecs_get_object(world, e, rel, 0) == 0); + + ecs_fini(world); +} + +void Pairs_get_n_objects() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + ecs_entity_t rel = ecs_new_id(world); + ecs_entity_t obj_1 = ecs_new_id(world); + ecs_entity_t obj_2 = ecs_new_id(world); + ecs_entity_t obj_3 = ecs_new_id(world); + + ecs_add_pair(world, e, rel, obj_1); + ecs_add_pair(world, e, rel, obj_2); + ecs_add_pair(world, e, rel, obj_3); + test_assert(ecs_has_pair(world, e, rel, obj_1)); + test_assert(ecs_has_pair(world, e, rel, obj_2)); + test_assert(ecs_has_pair(world, e, rel, obj_3)); + test_assert(ecs_get_object(world, e, rel, 0) == obj_1); + test_assert(ecs_get_object(world, e, rel, 1) == obj_2); + test_assert(ecs_get_object(world, e, rel, 2) == obj_3); + test_assert(ecs_get_object(world, e, rel, 3) == 0); + + ecs_fini(world); +} + diff --git a/fggl/ecs2/flecs/test/api/src/Parser.c b/fggl/ecs2/flecs/test/api/src/Parser.c new file mode 100644 index 0000000000000000000000000000000000000000..d13df0f6132a39479634db323503bcc22c115a8a --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Parser.c @@ -0,0 +1,2852 @@ +#include <api.h> + +static +int filter_count(ecs_filter_t *f) { + test_assert(f != NULL); + return f->term_count; +} + +static +ecs_term_t* filter_terms(ecs_filter_t *f) { + test_assert(f != NULL); + return f->terms; +} + +#define test_pred(column, e, isa)\ + test_int(column.pred.entity, e);\ + test_int(column.pred.set.mask, isa); + +#define test_subj(column, e, isa)\ + test_int(column.args[0].entity, e);\ + test_int(column.args[0].set.mask, isa); + +#define test_obj(column, e, isa)\ + test_int(column.args[1].entity, e);\ + test_int(column.args[1].set.mask, isa); + +#define test_pred_var(column, e, isa, str)\ + test_pred(column, e, isa);\ + test_str(column.pred.name, str); + +#define test_subj_var(column, e, isa, str)\ + test_subj(column, e, isa);\ + test_str(column.args[0].name, str); + +#define test_obj_var(column, e, isa, str)\ + test_obj(column, e, isa);\ + test_str(column.args[1].name, str); + +#define test_legacy(f)\ +{\ + int32_t i, count = filter_count(&f);\ + ecs_term_t *terms = filter_terms(&f);\ + for (i = 0; i < count; i ++) {\ + ecs_term_t *term = &terms[i];\ + if (term->oper != EcsOr) {\ + if (term->args[1].entity) {\ + if (term->role) {\ + test_int(ECS_ROLE_MASK & term->id, term->role);\ + } else {\ + test_assert(ECS_HAS_ROLE(term->id, PAIR));\ + }\ + test_int(ECS_PAIR_RELATION(term->id), ecs_entity_t_lo(term->pred.entity));\ + test_int(ECS_PAIR_OBJECT(term->id), ecs_entity_t_lo(term->args[1].entity));\ + } else {\ + test_int(ECS_COMPONENT_MASK & term->id, term->pred.entity);\ + test_int(ECS_ROLE_MASK & term->id, term->role);\ + }\ + }\ + }\ +}\ + +void Parser_resolve_this() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_lookup_fullpath(world, "."); + test_assert(e != 0); + test_assert(e == EcsThis); + + ecs_fini(world); +} + +void Parser_resolve_wildcard() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_lookup_fullpath(world, "*"); + test_assert(e != 0); + test_assert(e == EcsWildcard); + + ecs_fini(world); +} + +void Parser_resolve_is_a() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_lookup_fullpath(world, "IsA"); + test_assert(e != 0); + test_assert(e == EcsIsA); + + ecs_fini(world); +} + +void Parser_0() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "0" + })); + + test_int(filter_count(&f), 0); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(Subj)" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_explicit_subject_this() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(.)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_explicit_subject_this_by_name() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(This)" + })); + + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_explicit_subject_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(*)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsWildcard, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_wildcard_pred() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(*, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], EcsWildcard, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_wildcard_obj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(Pred, *)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], EcsWildcard, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_this_pred() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(., Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], EcsThis, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_this_obj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(Pred, .)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(Subj, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_this() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(., Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_this_by_name() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(This, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_wildcard_pred() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "*(This, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], EcsWildcard, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_wildcard_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(*, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsWildcard, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_wildcard_obj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(This, *)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], EcsWildcard, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_in_component_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[in] Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsIn); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_in_component_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[in] Pred(Subj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsIn); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_in_pair_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[in] (Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsIn); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_in_pair_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[in] Pred(Subj, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsIn); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_component_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_component_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] Pred(Subj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_pair_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] (Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_pair_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] Pred(Subj, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_out_component_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[out] Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_out_component_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[out] Pred(Subj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_out_pair_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[out] (Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_out_pair_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[out] Pred(Subj, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsOut); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_singleton() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "$Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Pred, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_this_singleton() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "$." + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], EcsThis, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_implicit_no_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred()" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_explicit_no_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(0)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_no_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(0, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_variable_single_char() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, X); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(X)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj_var(terms[0], 0, EcsDefaultSet, "X"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_variable_multi_char() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, XYZ); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(XYZ)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj_var(terms[0], 0, EcsDefaultSet, "XYZ"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_variable_multi_char_w_underscore() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, XY_Z); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(XY_Z)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj_var(terms[0], 0, EcsDefaultSet, "XY_Z"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_variable_multi_char_w_number() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, XY_1); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(XY_1)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj_var(terms[0], 0, EcsDefaultSet, "XY_1"); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_escaped_all_caps_single_char() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, X); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(\\X)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], X, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_escaped_all_caps_multi_char() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, XYZ); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(\\XYZ)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], XYZ, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_not() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "!Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_not() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "!(Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_not() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "!Pred(Subj, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_component_not() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "!Pred_1, !Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsNot); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_component_not_no_space() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "!Pred_1,!Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsNot); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsNot); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_component_optional() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "?Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOptional); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_component_optional() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "?Pred_1, ?Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOptional); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsOptional); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_component_optional_no_space() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "?Pred_1,?Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOptional); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsOptional); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_from_and() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "AND | Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAndFrom); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_from_or() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "OR | Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOrFrom); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_from_not() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "NOT | Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsNotFrom); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_optional() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "?(Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsOptional); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_optional() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Subj); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "?Pred(Subj, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], Subj, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsOptional); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "DISABLED | Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_explicit_subject_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "DISABLED | Pred(.)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_no_subject_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "DISABLED | Pred()" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_implicit_subject_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "PAIR | (Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_PAIR); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pair_explicit_subject_w_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "PAIR | Pred(., Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_PAIR); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_role_pred_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] DISABLED | Pred" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + test_int(terms[0].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_role_pred_no_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] DISABLED | Pred()" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + test_int(terms[0].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_role_pred_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] DISABLED | Pred(This)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + test_int(terms[0].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_role_pair_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] PAIR | (Pred, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + test_int(terms[0].role, ECS_PAIR); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_inout_role_pair_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] PAIR | Pred(This, Obj)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOut); + test_int(terms[0].role, ECS_PAIR); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pred_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1, Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pred_no_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1(), Pred_2()" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], 0, EcsNothing); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pred_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1(This), Pred_2(This)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pair_implicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(Pred_1, Obj_1), (Pred_2, Obj_2)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj_1, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_obj(terms[1], Obj_2, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pair_explicit_subject() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1(This, Obj_1), Pred_2(This, Obj_2)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj_1, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_obj(terms[1], Obj_2, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pred_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "DISABLED | Pred_1, DISABLED | Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_DISABLED); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + test_int(terms[1].role, ECS_DISABLED); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pair_implicit_subj_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "PAIR | (Pred_1, Obj_1), PAIR | (Pred_2, Obj_2)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj_1, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_PAIR); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_obj(terms[1], Obj_2, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + test_int(terms[1].role, ECS_PAIR); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_pair_explicit_subj_role() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "PAIR | Pred_1(This, Obj_1), PAIR | Pred_2(This, Obj_2)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj_1, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].role, ECS_PAIR); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_obj(terms[1], Obj_2, EcsDefaultSet); + test_int(terms[1].oper, EcsAnd); + test_int(terms[1].inout, EcsInOutDefault); + test_int(terms[1].role, ECS_PAIR); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_or_pred_implicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1 || Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOr); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsOr); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_or_pred_explicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1(This) || Pred_2(This)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOr); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsOr); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_or_pair_implicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(Pred_1, Obj_1) || (Pred_2, Obj_2)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj_1, EcsDefaultSet); + test_int(terms[0].oper, EcsOr); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_obj(terms[1], Obj_2, EcsDefaultSet); + test_int(terms[1].oper, EcsOr); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_or_pair_explicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + ECS_TAG(world, Obj_1); + ECS_TAG(world, Obj_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred_1(This, Obj_1) || Pred_2(This, Obj_2)" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], Obj_1, EcsDefaultSet); + test_int(terms[0].oper, EcsOr); + test_int(terms[0].inout, EcsInOutDefault); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_obj(terms[1], Obj_2, EcsDefaultSet); + test_int(terms[1].oper, EcsOr); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_2_or_pred_inout() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "[inout] Pred_1 || Pred_2" + })); + test_int(filter_count(&f), 2); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred_1, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsOr); + test_int(terms[0].inout, EcsInOut); + + test_pred(terms[1], Pred_2, EcsDefaultSet); + test_subj(terms[1], EcsThis, EcsDefaultSet); + test_int(terms[1].oper, EcsOr); + test_int(terms[1].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_1_digit_pred_implicit_subj() { + ecs_world_t *world = ecs_init(); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "100" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], 100, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_1_digit_pred_no_subj() { + ecs_world_t *world = ecs_init(); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "100()" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], 100, EcsDefaultSet); + test_subj(terms[0], 0, EcsNothing); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_1_digit_pred_explicit_subj() { + ecs_world_t *world = ecs_init(); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "100(200)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], 100, EcsDefaultSet); + test_subj(terms[0], 200, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_1_digit_pair_implicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "(100, 300)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], 100, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_obj(terms[0], 300, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_1_digit_pair_explicit_subj() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred_1); + ECS_TAG(world, Pred_2); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "100(200, 300)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], 100, EcsDefaultSet); + test_subj(terms[0], 200, EcsDefaultSet); + test_obj(terms[0], 300, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_self() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(self)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSelf); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_subset() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(subset)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSubSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset_inclusive() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(self|superset)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSelf | EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_subset_inclusive() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(self|subset)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSelf | EcsSubSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset_cascade() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset|cascade)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_subset_cascade() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(subset|cascade)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSubSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset_inclusive_cascade() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset|cascade|self)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSelf | EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_subset_inclusive_cascade() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(subset|cascade|self)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSelf | EcsSubSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_implicit_superset_cascade() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .expr = "Pred(cascade)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_implicit_superset_inclusive_cascade() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(self|cascade)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsSelf | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsIsA); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_implicit_superset_cascade_w_rel() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Rel); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .expr = "Pred(cascade(Rel))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, Rel); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_implicit_superset_inclusive_cascade_w_rel() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Rel); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(self|cascade(Rel))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsSelf | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, Rel); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + + +void Parser_pred_implicit_subject_superset_depth_1_digit() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset(2))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.max_depth, 2); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_subset_depth_1_digit() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(subset(2))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSubSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.max_depth, 2); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset_depth_2_digits() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset(20))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.max_depth, 20); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_subset_depth_2_digits() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(subset(20))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSubSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.max_depth, 20); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_superset_min_max_depth() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset(2, 3))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.min_depth, 2); + test_int(terms[0].args[0].set.max_depth, 3); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_superset_childof_min_max_depth() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset(ChildOf, 2, 3))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.min_depth, 2); + test_int(terms[0].args[0].set.max_depth, 3); + test_int(terms[0].args[0].set.relation, EcsChildOf); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + + +void Parser_pred_implicit_subject_superset_childof() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset(ChildOf))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsChildOf); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_cascade_superset_childof() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(cascade|superset(ChildOf))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsChildOf); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset_cascade_childof() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "Pred(superset|cascade(ChildOf))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsChildOf); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_pred_implicit_subject_superset_cascade_childof_optional() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "?Pred(superset|cascade(ChildOf))" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], Pred, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsSuperSet | EcsCascade); + test_int(terms[0].oper, EcsOptional); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].args[0].set.relation, EcsChildOf); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void Parser_expr_w_symbol() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + + ecs_entity_t comp = ecs_component_init(world, &(ecs_component_desc_t) { + .entity = { + .name = "Foo", + .symbol = "FooBar" + } + }); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){ + .expr = "FooBar" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_pred(terms[0], comp, EcsDefaultSet); + test_subj(terms[0], EcsThis, EcsDefaultSet); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + + test_legacy(f); + + ecs_filter_fini(&f); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Pipeline.c b/fggl/ecs2/flecs/test/api/src/Pipeline.c new file mode 100644 index 0000000000000000000000000000000000000000..26a80ef242a9b76bdfaecda74e03d8cdf1a6c004 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Pipeline.c @@ -0,0 +1,883 @@ +#include <api.h> + +void Pipeline_setup() { + ecs_tracing_enable(-3); +} + +static int sys_a_invoked; +static int sys_b_invoked; +static int sys_c_invoked; + +void SysA(ecs_iter_t *it) { + sys_a_invoked ++; +} +void SysB(ecs_iter_t *it) { + test_assert(sys_a_invoked != 0); + sys_b_invoked ++; +} +void SysC(ecs_iter_t *it) { + test_assert(sys_b_invoked != 0); + sys_c_invoked ++; +} + +void Pipeline_system_order_same_phase() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_same_phase_after_disable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); + + ecs_enable(world, SysB, false); + test_assert( ecs_has_entity(world, SysB, EcsDisabled)); + ecs_enable(world, SysB, true); + test_assert( !ecs_has_entity(world, SysB, EcsDisabled)); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_different_phase() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysA, EcsPreUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_different_phase_after_disable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysA, EcsPreUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_enable(world, SysB, false); + test_assert( ecs_has_entity(world, SysB, EcsDisabled)); + ecs_enable(world, SysB, true); + test_assert( !ecs_has_entity(world, SysB, EcsDisabled)); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_same_phase_after_activate() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Velocity); /* System is deactivated */ + ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + test_assert( ecs_has_entity(world, SysB, EcsInactive)); + ecs_add(world, E, Velocity); + test_assert( !ecs_has_entity(world, SysB, EcsInactive)); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_different_phase_after_activate() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Velocity); /* System is deactivated */ + ECS_SYSTEM(world, SysA, EcsPreUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + test_assert( ecs_has_entity(world, SysB, EcsInactive)); + ecs_add(world, E, Velocity); + test_assert( !ecs_has_entity(world, SysB, EcsInactive)); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_after_new_system_lower_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ecs_entity_t Sys = ecs_new(world, 0); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Create new system with Sys id */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = Sys, .name = "SysA", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = SysA + }); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_after_new_system_inbetween_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ecs_entity_t Sys = ecs_new(world, 0); + ECS_SYSTEM(world, SysC, EcsOnUpdate, Position); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Create new system with Sys id */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = Sys, .name = "SysB", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = SysB + }); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_system_order_after_new_system_higher_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ecs_entity_t Sys = ecs_new(world, 0); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Create new system with Sys id */ + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = Sys, .name = "SysC", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = SysC + }); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +static int sys_out_invoked; +static int sys_in_invoked; + +static void SysOut(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 2); + + sys_out_invoked ++; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Velocity, {10, 20}); + } +} + +static void SysOutMain(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 2); + + sys_out_invoked ++; + + int i; + for (i = 0; i < it->count; i ++) { + v[i].x = 10; + v[i].y = 20; + } +} + +static void SysIn(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + test_assert(sys_out_invoked != 0); + sys_in_invoked ++; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + test_assert( ecs_has(it->world, e, Velocity)); + + const Velocity *v_ptr = ecs_get(it->world, e, Velocity); + test_int(v_ptr->x, 10); + test_int(v_ptr->y, 20); + } +} + +static void SysInMain(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + test_assert(sys_out_invoked != 0); + sys_in_invoked ++; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + test_assert( ecs_has(it->world, e, Velocity)); + + test_int(v[i].x, 10); + test_int(v[i].y, 20); + } +} + +void Pipeline_merge_after_staged_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [out] :Velocity); + ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 2); + test_int(stats->merge_count_total, 2); + test_int(stats->pipeline_build_count_total, 2); + + test_int(sys_out_invoked, 1); + test_int(sys_in_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 2); + + ecs_fini(world); +} + +void Pipeline_merge_after_not_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, !Velocity); + ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 2); + test_int(stats->merge_count_total, 2); + test_int(stats->pipeline_build_count_total, 2); + + test_int(sys_out_invoked, 1); + test_int(sys_in_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 2); + + ecs_fini(world); +} + +void Pipeline_no_merge_after_main_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position, Velocity); + + ECS_SYSTEM(world, SysOutMain, EcsOnUpdate, Position, Velocity); + ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 2); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_out_invoked, 1); + test_int(sys_in_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_no_merge_after_staged_in_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position, Velocity); + + ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, :Velocity); + ECS_SYSTEM(world, SysIn, EcsOnUpdate, :Velocity); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 2); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_out_invoked, 1); + test_int(sys_in_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 1); + + ecs_fini(world); +} + +void Pipeline_merge_after_staged_out_before_owned() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysOut, EcsOnUpdate, Position, [out] :Velocity); + ECS_SYSTEM(world, SysInMain, EcsOnUpdate, Velocity); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 2); + test_int(stats->merge_count_total, 2); + test_int(stats->pipeline_build_count_total, 2); + + test_int(sys_out_invoked, 1); + test_int(sys_in_invoked, 1); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 2); + + ecs_fini(world); +} + +void Pipeline_switch_pipeline() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); + + ECS_PIPELINE(world, P1, flecs.pipeline.OnUpdate, flecs.pipeline.PostUpdate); + ECS_PIPELINE(world, P2, flecs.pipeline.OnUpdate); + + ecs_set_pipeline(world, P1); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_progress(world, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_set_pipeline(world, P2); + + ecs_progress(world, 1); + test_int(stats->pipeline_build_count_total, 2); + + test_int(sys_a_invoked, 2); + test_int(sys_b_invoked, 2); + test_int(sys_c_invoked, 1); + + ecs_fini(world); +} + +void Pipeline_run_pipeline() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, E, Position); + + ECS_SYSTEM(world, SysA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysB, EcsOnUpdate, Position); + ECS_SYSTEM(world, SysC, EcsPostUpdate, Position); + + ECS_PIPELINE(world, P1, flecs.pipeline.OnUpdate, flecs.pipeline.PostUpdate); + ECS_PIPELINE(world, P2, flecs.pipeline.OnUpdate); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + ecs_pipeline_run(world, P1, 1); + + test_int(stats->systems_ran_frame, 3); + test_int(stats->merge_count_total, 1); + test_int(stats->pipeline_build_count_total, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_pipeline_run(world, P2, 1); + + test_int(stats->pipeline_build_count_total, 2); + + test_int(sys_a_invoked, 2); + test_int(sys_b_invoked, 2); + test_int(sys_c_invoked, 1); + + ecs_fini(world); +} + +void Pipeline_get_pipeline_from_stage() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t pipeline = ecs_get_pipeline(world); + test_assert(pipeline != 0); + + /* Get default stage */ + ecs_world_t *stage = ecs_get_stage(world, 0); + test_assert(stage != NULL); + + test_assert(pipeline == ecs_get_pipeline(stage)); + + ecs_fini(world); +} + +void Pipeline_3_systems_3_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "SysA", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = SysA + }); + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = NULL, .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = SysB + }); + ecs_entity_t s3 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "SysC", .add = {EcsOnUpdate} }, + .query.filter.expr = ":Position", + .callback = SysC + }); + + test_assert(s1 != 0); + test_assert(s2 != 0); + test_assert(s3 != 0); + + ecs_add_id(world, s3, Tag); + + ecs_new(world, Position); + + ecs_progress(world, 1); + + test_int(sys_a_invoked, 1); + test_int(sys_b_invoked, 1); + test_int(sys_c_invoked, 1); + + ecs_fini(world); +} + +static +void RandomWrite(ecs_iter_t *it) { + ecs_entity_t ecs_id(Position) = ecs_term_id(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Position, {1, 2}); + } +} + +static +void RandomRead(ecs_iter_t *it) { + ecs_entity_t ecs_id(Position) = ecs_lookup(it->world, "Position"); + + int i; + for (i = 0; i < it->count; i ++) { + const Position *p = ecs_get(it->world, it->entities[i], Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + } +} + +static +void RandomReadWrite(ecs_iter_t *it) { + ecs_entity_t ecs_id(Position) = ecs_lookup(it->world, "Position"); + + int i; + for (i = 0; i < it->count; i ++) { + const Position *p = ecs_get(it->world, it->entities[i], Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_set(it->world, it->entities[i], Position, {p->x + 1, p->y + 1}); + } +} + +static +void RandomReadAfterRW(ecs_iter_t *it) { + ecs_entity_t ecs_id(Position) = ecs_lookup(it->world, "Position"); + + int i; + for (i = 0; i < it->count; i ++) { + const Position *p = ecs_get(it->world, it->entities[i], Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + } +} + +static +void RandomRead_Not(ecs_iter_t *it) { + ecs_entity_t ecs_id(Position) = ecs_term_id(it, 2); + ecs_type_t ecs_type(Position) = ecs_column_type(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + test_assert(!ecs_has(it->world, it->entities[i], Position)); + const Position *p = ecs_get(it->world, it->entities[i], Position); + test_assert(p == NULL); + } +} + +void Pipeline_random_read_after_random_write_out_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] :Position); + ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] :Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_read_after_random_write_inout_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [inout] :Position); + ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] :Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_read_after_random_write_out_inout() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] :Position); + ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [inout] :Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_read_after_random_write_inout_inout() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [inout] :Position); + ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [inout] :Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_read_after_random_write_w_not_write() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] !Position); + ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] :Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_read_after_random_write_w_not_read() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] :Position); + ECS_SYSTEM(world, RandomRead_Not, EcsOnUpdate, Tag, [in] !Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_read_after_random_write_w_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] :Position); + ECS_SYSTEM(world, RandomRead, EcsOnUpdate, Tag, [in] :*); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Pipeline_random_in_after_random_inout_after_random_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, RandomWrite, EcsOnUpdate, Tag, [out] :Position); + ECS_SYSTEM(world, RandomReadWrite, EcsOnUpdate, Tag, [inout] :Position); + ECS_SYSTEM(world, RandomReadAfterRW, EcsOnUpdate, Tag, [in] :Position); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_progress(world, 1); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 2); + test_int(p->y, 3); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Plecs.c b/fggl/ecs2/flecs/test/api/src/Plecs.c new file mode 100644 index 0000000000000000000000000000000000000000..431a30b386022fe792955831fc3d0f2f463179e9 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Plecs.c @@ -0,0 +1,176 @@ +#include <api.h> + +void Plecs_null() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, NULL) == 0); + + ecs_fini(world); +} + +void Plecs_empty() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "") == 0); + + ecs_fini(world); +} + +void Plecs_space() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, " ") == 0); + + ecs_fini(world); +} + +void Plecs_space_newline() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, " \n \n") == 0); + + ecs_fini(world); +} + +void Plecs_empty_newline() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "\n\n") == 0); + + ecs_fini(world); +} + +void Plecs_entity() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "Foo") == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + test_str(ecs_get_name(world, foo), "Foo"); + test_int(ecs_vector_count(ecs_get_type(world, foo)), 1); + + ecs_fini(world); +} + +void Plecs_entity_w_entity() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "Foo(Subj)") == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + test_str(ecs_get_name(world, foo), "Foo"); + + ecs_entity_t subj = ecs_lookup(world, "Subj"); + test_assert(subj != 0); + test_str(ecs_get_name(world, subj), "Subj"); + + test_assert(ecs_has_id(world, subj, foo)); + + ecs_fini(world); +} + +void Plecs_entity_w_pair() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "Rel(Subj, Obj)") == 0); + + ecs_entity_t rel = ecs_lookup(world, "Rel"); + test_assert(rel != 0); + test_str(ecs_get_name(world, rel), "Rel"); + + ecs_entity_t obj = ecs_lookup(world, "Obj"); + test_assert(obj != 0); + test_str(ecs_get_name(world, obj), "Obj"); + + ecs_entity_t subj = ecs_lookup(world, "Subj"); + test_assert(subj != 0); + test_str(ecs_get_name(world, subj), "Subj"); + + test_assert(ecs_has_pair(world, subj, rel, obj)); + + ecs_fini(world); +} + +void Plecs_2_entities() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "Foo\nBar\n") == 0); + + ecs_entity_t e = ecs_lookup(world, "Foo"); + test_assert(e != 0); + test_str(ecs_get_name(world, e), "Foo"); + + ecs_fini(world); +} + +void Plecs_2_entities_w_entities() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, "Foo(Subj_1)\nBar(Subj_2)\n") == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + test_assert(foo != 0); + test_str(ecs_get_name(world, foo), "Foo"); + + ecs_entity_t bar = ecs_lookup(world, "Bar"); + test_assert(bar != 0); + test_str(ecs_get_name(world, bar), "Bar"); + + ecs_entity_t subj_1 = ecs_lookup(world, "Subj_1"); + test_assert(subj_1 != 0); + test_str(ecs_get_name(world, subj_1), "Subj_1"); + + ecs_entity_t subj_2 = ecs_lookup(world, "Subj_2"); + test_assert(subj_2 != 0); + test_str(ecs_get_name(world, subj_2), "Subj_2"); + + test_assert(ecs_has_id(world, subj_1, foo)); + test_assert(ecs_has_id(world, subj_2, bar)); + + ecs_fini(world); +} + +void Plecs_3_entities_w_pairs() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_plecs_from_str(world, NULL, + "Rel_1(Subj_1, Obj_1)\n" + "Rel_1(Subj_2, Obj_2)\n" + "Rel_2(Subj_3, Obj_2)\n") == 0); + + ecs_entity_t rel_1 = ecs_lookup(world, "Rel_1"); + test_assert(rel_1 != 0); + test_str(ecs_get_name(world, rel_1), "Rel_1"); + + ecs_entity_t rel_2 = ecs_lookup(world, "Rel_2"); + test_assert(rel_2 != 0); + test_str(ecs_get_name(world, rel_2), "Rel_2"); + + ecs_entity_t obj_1 = ecs_lookup(world, "Obj_1"); + test_assert(obj_1 != 0); + test_str(ecs_get_name(world, obj_1), "Obj_1"); + + ecs_entity_t obj_2 = ecs_lookup(world, "Obj_2"); + test_assert(obj_2 != 0); + test_str(ecs_get_name(world, obj_2), "Obj_2"); + + ecs_entity_t subj_1 = ecs_lookup(world, "Subj_1"); + test_assert(subj_1 != 0); + test_str(ecs_get_name(world, subj_1), "Subj_1"); + + ecs_entity_t subj_2 = ecs_lookup(world, "Subj_2"); + test_assert(subj_2 != 0); + test_str(ecs_get_name(world, subj_2), "Subj_2"); + + ecs_entity_t subj_3 = ecs_lookup(world, "Subj_3"); + test_assert(subj_3 != 0); + test_str(ecs_get_name(world, subj_3), "Subj_3"); + + test_assert(ecs_has_pair(world, subj_1, rel_1, obj_1)); + test_assert(ecs_has_pair(world, subj_2, rel_1, obj_2)); + test_assert(ecs_has_pair(world, subj_3, rel_2, obj_2)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Prefab.c b/fggl/ecs2/flecs/test/api/src/Prefab.c new file mode 100644 index 0000000000000000000000000000000000000000..6832f889040d71a7d9e398bad7cb894d19329b50 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Prefab.c @@ -0,0 +1,3433 @@ +#include <api.h> +#include <flecs/type.h> + +static ecs_id_t NamePair; + +void Prefab_setup() { + NamePair = ecs_pair(ecs_id(EcsIdentifier), EcsName); +} + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m_ptr, 1); + bool shared = !ecs_is_owned(it, 1); + + ECS_COLUMN(it, Position, p, 2); + + Velocity *v = NULL; + if (it->column_count >= 3) { + v = ecs_term(it, Velocity, 3); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + Mass m = 1; + if (m_ptr) { + if (shared) { + m = *m_ptr; + } else { + m = m_ptr[i]; + } + } + + p[i].x = 10 * m; + p[i].y = 20 * m; + + if (v) { + v[i].x = 30 * m; + v[i].y = 40 * m; + } + } +} + +void Prefab_new_w_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + ecs_add(world, e1, Velocity); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has_pair(world, e2, EcsIsA, Prefab)); + test_assert( !ecs_has_entity(world, e2, EcsPrefab)); + test_assert( !ecs_has_id(world, e2, NamePair)); + test_assert( !ecs_get_id(world, e2, NamePair)); + + ecs_add(world, e2, Velocity); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has_pair(world, e2, EcsIsA, Prefab)); + test_assert( !ecs_has_entity(world, e2, EcsPrefab)); + test_assert( !ecs_has_id(world, e2, NamePair)); + test_assert( !ecs_get_id(world, e2, NamePair)); + + const Position *p_1 = ecs_get(world, e1, Position); + const Position *p_2 = ecs_get(world, e2, Position); + + test_assert(p_1 != NULL); + test_assert(p_2 != NULL); + test_assert(p_1 == p_2); + + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Velocity *v_1 = ecs_get(world, e1, Velocity); + const Velocity *v_2 = ecs_get(world, e2, Velocity); + + test_assert(v_1 != NULL); + test_assert(v_2 != NULL); + test_assert(v_1 != v_2); + + ecs_fini(world); +} + +void Prefab_new_w_count_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + const ecs_entity_t *ids = ecs_bulk_new_w_entity(world, ecs_pair(EcsIsA, Prefab), 10); + test_assert(ids != NULL); + + ecs_entity_t i; + const Position *p_prev = NULL; + for (i = 0; i < 10; i ++) { + ecs_entity_t e = ids[i]; + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + + if (p_prev) test_ptr(p, p_prev); + test_int(p->x, 10); + test_int(p->y, 20); + + p_prev = p; + } + + ecs_fini(world); +} + +void Prefab_new_w_type_w_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position); + ECS_TYPE(world, Type, (IsA, Prefab), Velocity); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + + ecs_entity_t e2 = ecs_new(world, Type); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has_pair(world, e2, EcsIsA, Prefab)); + test_assert( !ecs_has_entity(world, e2, EcsPrefab)); + test_assert( !ecs_has_id(world, e2, NamePair)); + + const Position *p_1 = ecs_get(world, e1, Position); + const Position *p_2 = ecs_get(world, e2, Position); + const Position *p_prefab = ecs_get(world, Prefab, Position); + + test_assert(p_1 != NULL); + test_assert(p_2 != NULL); + test_assert(p_1 == p_2); + test_assert(p_1 == p_prefab); + + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Velocity *v_1 = ecs_get(world, e1, Velocity); + const Velocity *v_2 = ecs_get(world, e2, Velocity); + + test_assert(v_1 != NULL); + test_assert(v_2 != NULL); + test_assert(v_1 != v_2); + + ecs_fini(world); +} + +void Prefab_add_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new(world, Velocity); + test_assert(e1 != 0); + + ecs_add_pair(world, e1, EcsIsA, Prefab); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + + ecs_entity_t e2 = ecs_new(world, Velocity); + test_assert(e2 != 0); + + ecs_add_pair(world, e2, EcsIsA, Prefab); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has_pair(world, e2, EcsIsA, Prefab)); + test_assert( !ecs_has_entity(world, e2, EcsPrefab)); + test_assert( !ecs_has_id(world, e2, NamePair)); + + const Position *p_1 = ecs_get(world, e1, Position); + const Position *p_2 = ecs_get(world, e2, Position); + const Position *p_prefab = ecs_get(world, Prefab, Position); + + test_assert(p_1 != NULL); + test_assert(p_2 != NULL); + test_assert(p_1 == p_2); + test_assert(p_prefab == p_1); + + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Velocity *v_1 = ecs_get(world, e1, Velocity); + const Velocity *v_2 = ecs_get(world, e2, Velocity); + + test_assert(v_1 != NULL); + test_assert(v_2 != NULL); + test_assert(v_1 != v_2); + + ecs_fini(world); +} + +void Prefab_add_type_w_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position); + ECS_TYPE(world, Type, (IsA, Prefab), Velocity); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_add(world, e1, Type); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + + ecs_entity_t e2 = ecs_new(world, 0); + + ecs_add(world, e2, Type); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has_pair(world, e2, EcsIsA, Prefab)); + test_assert( !ecs_has_entity(world, e2, EcsPrefab)); + test_assert( !ecs_has_id(world, e2, NamePair)); + + const Position *p_1 = ecs_get(world, e1, Position); + const Position *p_2 = ecs_get(world, e2, Position); + const Position *p_prefab = ecs_get(world, Prefab, Position); + + test_assert(p_1 != NULL); + test_assert(p_2 != NULL); + test_assert(p_1 == p_2); + test_assert(p_prefab == p_1); + + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Velocity *v_1 = ecs_get(world, e1, Velocity); + const Velocity *v_2 = ecs_get(world, e2, Velocity); + + test_assert(v_1 != NULL); + test_assert(v_2 != NULL); + test_assert(v_1 != v_2); + + ecs_fini(world); +} + +void Prefab_remove_prefab_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + ecs_remove_pair(world, e1, EcsIsA, Prefab); + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + ecs_fini(world); +} + +void Prefab_remove_prefab_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_add_pair(world, e1, EcsIsA, Prefab); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + ecs_remove_pair(world, e1, EcsIsA, Prefab); + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + ecs_fini(world); +} + +void Prefab_override_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_set(world, e1, Position, {20, 30}); + p = ecs_get(world, e1, Position); + test_int(p->x, 20); + test_int(p->y, 30); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + ecs_fini(world); +} + +void Prefab_override_remove_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_set(world, e1, Position, {20, 30}); + p = ecs_get(world, e1, Position); + test_int(p->x, 20); + test_int(p->y, 30); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p_prefab != NULL); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + ecs_remove(world, e1, Position); + p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_assert(p == p_prefab); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Prefab_override_2_of_3_components_1_self() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_PREFAB(world, Prefab, Position, Velocity, Mass); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + ecs_set(world, Prefab, Mass, {50}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e1 != 0); + + ecs_set(world, e1, Rotation, {60}); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e1, Mass)); + test_assert( ecs_has(world, e1, Rotation)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_set(world, e1, Position, {20, 30}); + p = ecs_get(world, e1, Position); + test_int(p->x, 20); + test_int(p->y, 30); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_set(world, e1, Velocity, {40, 50}); + v = ecs_get(world, e1, Velocity); + test_int(v->x, 40); + test_int(v->y, 50); + + const Velocity *v_prefab = ecs_get(world, Prefab, Velocity); + test_assert(v != v_prefab); + test_int(v_prefab->x, 30); + test_int(v_prefab->y, 40); + + const Mass *m = ecs_get(world, e1, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + const Mass *m_prefab = ecs_get(world, Prefab, Mass); + test_assert(m_prefab != NULL); + test_ptr(m_prefab, m); + + const Rotation *r = ecs_get(world, e1, Rotation); + test_assert(r != NULL); + test_int(*r, 60); + + ecs_fini(world); +} + +void Prefab_new_type_w_1_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position, Velocity); + ECS_TYPE(world, Type, (IsA, Prefab), Position); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + test_assert( ecs_has(world, e1, Type)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p_prefab != NULL); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + const Velocity *v_prefab = ecs_get(world, Prefab, Velocity); + test_assert(v_prefab != NULL); + test_assert(v == v_prefab); + test_int(v_prefab->x, 30); + test_int(v_prefab->y, 40); + + ecs_fini(world); +} + +void Prefab_new_type_w_2_overrides() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position, Velocity); + ECS_TYPE(world, Type, (IsA, Prefab), Position, Velocity); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + test_assert( ecs_has(world, e1, Type)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p_prefab != NULL); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + const Velocity *v_prefab = ecs_get(world, Prefab, Velocity); + test_assert(v_prefab != NULL); + test_assert(v != v_prefab); + test_int(v_prefab->x, 30); + test_int(v_prefab->y, 40); + + ecs_fini(world); +} + +void Prefab_add_type_w_1_overrides() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position, Velocity); + ECS_TYPE(world, Type, (IsA, Prefab), Position); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + + ecs_entity_t e1 = ecs_new(world, 0); + test_assert(e1 != 0); + + ecs_add(world, e1, Type); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + test_assert( ecs_has(world, e1, Type)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p_prefab != NULL); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + const Velocity *v_prefab = ecs_get(world, Prefab, Velocity); + test_assert(v_prefab != NULL); + test_assert(v == v_prefab); + test_int(v_prefab->x, 30); + test_int(v_prefab->y, 40); + + ecs_fini(world); +} + +void Prefab_add_type_w_2_overrides() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position, Velocity); + ECS_TYPE(world, Type, (IsA, Prefab), Position, Velocity); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + + ecs_add(world, e1, Type); + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + test_assert( ecs_has(world, e1, Type)); + /* These components should never be inherited from prefabs */ + test_assert( !ecs_has_entity(world, e1, EcsPrefab)); + test_assert( !ecs_has_id(world, e1, NamePair)); + test_assert( !ecs_get_id(world, e1, NamePair)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Position *p_prefab = ecs_get(world, Prefab, Position); + test_assert(p_prefab != NULL); + test_assert(p != p_prefab); + test_int(p_prefab->x, 10); + test_int(p_prefab->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + const Velocity *v_prefab = ecs_get(world, Prefab, Velocity); + test_assert(v_prefab != NULL); + test_assert(v != v_prefab); + test_int(v_prefab->x, 30); + test_int(v_prefab->y, 40); + + ecs_fini(world); +} + +void Prefab_get_ptr_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + + ecs_set(world, Prefab, Position, {10, 20}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e1 != 0); + test_assert( ecs_has_pair(world, e1, EcsIsA, Prefab)); + test_assert( ecs_get_id(world, e1, Prefab) == NULL); + + ecs_fini(world); +} + +static +void Prefab_w_shared(ecs_iter_t *it) { + Velocity *v = NULL; + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + if (v) { + test_assert(!ecs_is_owned(it, 2)); + } + } + + Mass *m = NULL; + if (it->column_count >= 3) { + m = ecs_term(it, Mass, 3); + } + + probe_system(it); + + Position *pos = ecs_term(it, Position, 1); + + for (int i = 0; i < it->count; i ++) { + Position *p = &pos[i]; + p->x += v->x; + p->y += v->y; + + if (m) { + p->x += *m; + p->y += *m; + } + } +} + +void Prefab_iterate_w_prefab_shared() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Velocity); + ECS_TYPE(world, Type, (IsA, Prefab), Position); + ECS_SYSTEM(world, Prefab_w_shared, EcsOnUpdate, Position, ANY:Velocity); + + ecs_set(world, Prefab, Velocity, {1, 2}); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + ecs_set(world, e1, Position, {0, 0}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Prefab_w_shared); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], Prefab); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Prefab_match_entity_prefab_w_system_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Velocity, Mass); + ECS_TYPE(world, Type, (IsA, Prefab), Position); + ECS_SYSTEM(world, Prefab_w_shared, EcsOnUpdate, Position, ANY:Velocity, ?ANY:Mass); + + ecs_set(world, Prefab, Velocity, {1, 2}); + ecs_set(world, Prefab, Mass, {3}); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + ecs_set(world, e1, Position, {0, 0}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Prefab_w_shared); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], Prefab); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], Prefab); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 5); + + ecs_fini(world); +} + +void Prefab_prefab_in_system_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab1, Velocity); + ECS_PREFAB(world, Prefab2, Velocity); + ECS_SYSTEM(world, Prefab_w_shared, EcsOnUpdate, Position, ANY:Velocity, Mass, (IsA, Prefab1)); + + ecs_set(world, Prefab1, Velocity, {1, 2}); + ecs_set(world, Prefab2, Velocity, {1, 2}); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab1); + ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, Prefab1); + ecs_entity_t e3 = ecs_new_w_pair(world, EcsIsA, Prefab2); + + test_assert(e1 != 0); + test_assert(e2 != 0); + test_assert(e3 != 0); + + ecs_set(world, e1, Position, {0, 0}); + ecs_set(world, e2, Position, {0, 0}); + ecs_set(world, e3, Position, {0, 0}); + + ecs_set(world, e1, Mass, {0}); + ecs_set(world, e2, Mass, {0}); + ecs_set(world, e3, Mass, {0}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Prefab_w_shared); + test_int(ctx.column_count, 4); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], Prefab1); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + test_int(ctx.c[0][3], ecs_pair(EcsIsA, Prefab1)); + test_int(ctx.s[0][3], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +static +void Dummy(ecs_iter_t *it) { + probe_system(it); +} + +void Prefab_dont_match_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, Prefab, Position); + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Prefab_new_w_count_w_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Position, Velocity); + ECS_TYPE(world, Type, (IsA, Prefab), Velocity); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 100); + test_assert(ids != NULL); + + const Position *prefab_p = ecs_get(world, Prefab, Position); + const Velocity *prefab_v = ecs_get(world, Prefab, Velocity); + + int i; + for (i = 0; i < 100; i ++) { + ecs_entity_t e = ids[i]; + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + test_assert( ecs_has_pair(world, e, EcsIsA, Prefab)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == prefab_p); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_assert(v != prefab_v); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Prefab_override_2_components_different_size() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Color); + ECS_PREFAB(world, Prefab, Position, Velocity, Color); + ECS_TYPE(world, Type, (IsA, Prefab), Velocity, Color); + + ecs_set(world, Prefab, Position, {10, 20}); + ecs_set(world, Prefab, Velocity, {30, 40}); + ecs_set(world, Prefab, Color, {1, 2, 3, 4}); + + // TODO: investigate crash when replacing ecs_bulk_new with + // ecs_new_w_entity_w_count + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 100); + test_assert(ids != NULL); + + const Position *prefab_p = ecs_get(world, Prefab, Position); + const Velocity *prefab_v = ecs_get(world, Prefab, Velocity); + const Color *prefab_c = ecs_get(world, Prefab, Color); + + test_assert(prefab_p != NULL); + test_assert(prefab_v != NULL); + test_assert(prefab_c != NULL); + + int i; + for (i = 0; i < 100; i ++) { + ecs_entity_t e = ids[i]; + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + test_assert( ecs_has_pair(world, e, EcsIsA, Prefab)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + test_assert(p == prefab_p); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + test_assert(v != prefab_v); + + const Color *c = ecs_get(world, e, Color); + test_assert(c != NULL); + test_int(c->r, 1); + test_int(c->g, 2); + test_int(c->b, 3); + test_int(c->a, 4); + test_assert(c != prefab_c); + } + + ecs_fini(world); +} + +void Prefab_ignore_prefab_parent_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, parent, Prefab, Position); + ECS_ENTITY(world, child, Prefab, (ChildOf, parent), Velocity); + + test_assert( ecs_has_pair(world, child, EcsChildOf, parent)); + test_assert( !ecs_has(world, child, Position)); + test_assert( ecs_has(world, child, Velocity)); + + ecs_fini(world); +} + +static +void Move(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += v[i].y; + p[i].y += v[i].y; + } +} + +static +void AddVelocity(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Velocity, {1, 1}); + } +} + +void Prefab_match_table_created_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Mass); + ecs_set(world, Prefab, Mass, {10}); + + ECS_ENTITY(world, e, (IsA, Prefab), Position); + + ECS_SYSTEM(world, AddVelocity, EcsOnUpdate, Position, !Velocity); + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity, ANY:Mass); + + ecs_set(world, e, Position, {0, 0}); + + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_has(world, e, Velocity)); + test_assert( ecs_has(world, e, Mass)); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 1); + + ecs_progress(world, 1); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 2); + + ecs_fini(world); +} + +void Prefab_prefab_w_1_child() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, Parent, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_PREFAB(world, Child, (ChildOf, Parent), Position); + ecs_set(world, Child, Position, {2, 3}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p_e = ecs_get(world, e, Position); + test_assert(p_e != NULL); + test_int(p_e->x, 1); + test_int(p_e->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child"); + test_assert(e_child != 0); + + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + const Position *p_child = ecs_get(world, e_child, Position); + test_assert(p_child != NULL); + test_int(p_child->x, 2); + test_int(p_child->y, 3); + + ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e2 != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_get(world, e2, Position) == p_e); + + ecs_entity_t e_child2 = ecs_lookup_child(world, e2, "Child"); + test_assert(e_child2 != 0); + test_assert(ecs_has(world, e_child2, Position)); + test_assert(ecs_has_pair(world, e_child2, EcsChildOf, e2)); + test_assert( ecs_get(world, e_child2, Position) != p_child); + + p_child = ecs_get(world, e_child2, Position); + test_assert(p_child != NULL); + test_int(p_child->x, 2); + test_int(p_child->y, 3); + + ecs_fini(world); +} + +void Prefab_prefab_w_2_children() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Parent, Prefab, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, Child1, Prefab, (ChildOf, Parent), Position); + ecs_set(world, Child1, Position, {2, 3}); + + ECS_ENTITY(world, Child2, Prefab, (ChildOf, Parent), Position); + ecs_set(world, Child2, Position, {3, 4}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child1"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + p = ecs_get(world, e_child, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + e_child = ecs_lookup_child(world, e, "Child2"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + p = ecs_get(world, e_child, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 4); + + ecs_fini(world); +} + +void Prefab_prefab_w_grandchild() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, Parent, Prefab, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, Child, Prefab, (ChildOf, Parent), Position); + ecs_set(world, Child, Position, {2, 3}); + + ECS_ENTITY(world, GrandChild, Prefab, (ChildOf, Parent.Child), Position, Rotation); + ecs_set(world, GrandChild, Position, {3, 4}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + p = ecs_get(world, e_child, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + ecs_entity_t e_grandchild = ecs_lookup_child(world, e_child, "GrandChild"); + test_assert(e_grandchild != 0); + test_assert(ecs_has(world, e_grandchild, Position)); + test_assert(ecs_has_pair(world, e_grandchild, EcsChildOf, e_child)); + + p = ecs_get(world, e_grandchild, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 4); + + ecs_fini(world); +} + +void Prefab_prefab_tree_1_2_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, parent, Prefab, Position); + ecs_set(world, parent, Position, {1, 2}); + + ECS_ENTITY(world, child_1, Prefab, (ChildOf, parent), Position); + ecs_set(world, child_1, Position, {2, 3}); + + ECS_ENTITY(world, child_2, Prefab, (ChildOf, parent), Position); + ecs_set(world, child_2, Position, {4, 5}); + + ECS_ENTITY(world, grandchild, Prefab, (ChildOf, parent.child_2), Position); + ecs_set(world, grandchild, Position, {6, 7}); + + test_assert(ecs_has_pair(world, child_1, EcsChildOf, parent)); + test_assert(ecs_has_pair(world, child_2, EcsChildOf, parent)); + test_assert(ecs_has_pair(world, grandchild, EcsChildOf, child_2)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "child_1"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + p = ecs_get(world, e_child, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + e_child = ecs_lookup_child(world, e, "child_2"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + p = ecs_get(world, e_child, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 5); + + ecs_entity_t e_grandchild = ecs_lookup_child(world, e_child, "grandchild"); + test_assert(e_grandchild != 0); + test_assert(ecs_has(world, e_grandchild, Position)); + test_assert(ecs_has_pair(world, e_grandchild, EcsChildOf, e_child)); + + p = ecs_get(world, e_grandchild, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 7); + + ecs_fini(world); +} + +void Prefab_prefab_w_base_w_child() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Base, Prefab, Velocity); + ecs_set(world, Base, Velocity, {3, 4}); + + ECS_ENTITY(world, Parent, Prefab, (IsA, Base), Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, Child, Prefab, (ChildOf, Parent), Position); + ecs_set(world, Child, Position, {2, 3}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + const Position *p_e = ecs_get(world, e, Position); + test_assert(p_e != NULL); + test_int(p_e->x, 1); + test_int(p_e->y, 2); + + const Velocity *v_e = ecs_get(world, e, Velocity); + test_assert(v_e != NULL); + test_int(v_e->x, 3); + test_int(v_e->y, 4); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(!ecs_has(world, e_child, Velocity)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + const Position *p_child = ecs_get(world, e_child, Position); + test_assert(p_child != NULL); + test_int(p_child->x, 2); + test_int(p_child->y, 3); + + ecs_fini(world); +} + +void Prefab_prefab_w_child_w_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Base, Prefab, Velocity); + ecs_set(world, Base, Velocity, {3, 4}); + + ECS_ENTITY(world, Parent, Prefab, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, Child, Prefab, (ChildOf, Parent), (IsA, Base), Position); + ecs_set(world, Child, Position, {2, 3}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p_e = ecs_get(world, e, Position); + test_assert(p_e != NULL); + test_int(p_e->x, 1); + test_int(p_e->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has(world, e_child, Velocity)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + const Position *p_child = ecs_get(world, e_child, Position); + test_assert(p_child != NULL); + test_int(p_child->x, 2); + test_int(p_child->y, 3); + + const Velocity *v_child = ecs_get(world, e_child, Velocity); + test_assert(v_child != NULL); + test_int(v_child->x, 3); + test_int(v_child->y, 4); + + ecs_fini(world); +} + +void Prefab_prefab_w_child_w_base_w_children() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Base, Prefab, Velocity); + ecs_set(world, Base, Velocity, {3, 4}); + + ECS_ENTITY(world, BaseChild, Prefab, (ChildOf, Base), Position); + ecs_set(world, BaseChild, Position, {4, 5}); + + ECS_ENTITY(world, Parent, Prefab, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, Child, Prefab, (ChildOf, Parent), (IsA, Base), Position); + ecs_set(world, Child, Position, {2, 3}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + + const Position *p_e = ecs_get(world, e, Position); + test_assert(p_e != NULL); + test_int(p_e->x, 1); + test_int(p_e->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has(world, e_child, Velocity)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + const Position *p_child = ecs_get(world, e_child, Position); + test_assert(p_child != NULL); + test_int(p_child->x, 2); + test_int(p_child->y, 3); + + const Velocity *v_child = ecs_get(world, e_child, Velocity); + test_assert(v_child != NULL); + test_int(v_child->x, 3); + test_int(v_child->y, 4); + + ecs_entity_t e_base_child = ecs_lookup_child(world, e_child, "BaseChild"); + test_assert(e_base_child != 0); + test_assert(ecs_has(world, e_base_child, Position)); + test_assert(!ecs_has(world, e_base_child, Velocity)); + test_assert(ecs_has_pair(world, e_base_child, EcsChildOf, e_child)); + + const Position *p_base_child = ecs_get(world, e_base_child, Position); + test_assert(p_base_child != NULL); + test_assert(p_base_child != p_child); + test_int(p_base_child->x, 4); + test_int(p_base_child->y, 5); + + ecs_fini(world); +} + +void Prefab_prefab_w_child_new_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Parent, Prefab, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, Child, Prefab, (ChildOf, Parent), Position); + ecs_set(world, Child, Position, {2, 3}); + + const ecs_entity_t *ids = ecs_bulk_new_w_entity(world, ecs_pair(EcsIsA, Parent), 3); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 3; i ++) { + ecs_entity_t e = ids[i]; + test_assert( ecs_has(world, e, Position)); + + const Position *p_e = ecs_get(world, e, Position); + test_assert(p_e != NULL); + test_int(p_e->x, 1); + test_int(p_e->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "Child"); + test_assert(e_child != 0); + test_assert(ecs_has(world, e_child, Position)); + test_assert(ecs_has_pair(world, e_child, EcsChildOf, e)); + + const Position *p_child = ecs_get(world, e_child, Position); + test_assert(p_child != NULL); + test_int(p_child->x, 2); + test_int(p_child->y, 3); + } + + ecs_fini(world); +} + +void Prefab_prefab_auto_override_child_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Parent, Prefab, Position); + ecs_set(world, Parent, Position, {1, 2}); + + ECS_ENTITY(world, ChildPrefab, Prefab, Position, Velocity); + ecs_set(world, ChildPrefab, Position, {2, 3}); + ecs_set(world, ChildPrefab, Velocity, {4, 5}); + + ECS_ENTITY(world, Child, Prefab, (ChildOf, Parent), (IsA, ChildPrefab), Velocity); + + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e1 != 0); + test_assert( ecs_has(world, e1, Position)); + + ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, Parent); + test_assert(e2 != 0); + test_assert( ecs_has(world, e2, Position)); + + const Position *p1 = ecs_get(world, e1, Position); + test_assert(p1 != NULL); + test_int(p1->x, 1); + test_int(p1->y, 2); + + const Position *p2 = ecs_get(world, e2, Position); + test_assert(p2 != NULL); + test_int(p2->x, 1); + test_int(p2->y, 2); + test_assert(p1 == p2); + + ecs_entity_t child_1 = ecs_lookup_child(world, e1, "Child"); + test_assert(child_1 != 0); + + test_assert( ecs_has(world, child_1, Position)); + test_assert( ecs_has(world, child_1, Velocity)); + test_assert( !ecs_has(world, child_1, EcsType)); + + ecs_entity_t child_2 = ecs_lookup_child(world, e2, "Child"); + test_assert(child_2 != 0); + test_assert( ecs_has(world, child_2, Position)); + test_assert( ecs_has(world, child_2, Velocity)); + test_assert( !ecs_has(world, child_2, EcsType)); + + const Position *child_p1 = ecs_get(world, child_1, Position); + test_assert(child_p1 != NULL); + test_int(child_p1->x, 2); + test_int(child_p1->y, 3); + + const Position *child_p2 = ecs_get(world, child_2, Position); + test_assert(child_p2 != NULL); + test_int(child_p2->x, 2); + test_int(child_p2->y, 3); + test_assert(child_p1 == child_p2); + + const Velocity *child_v1 = ecs_get(world, child_1, Velocity); + test_assert(child_v1 != NULL); + test_int(child_v1->x, 4); + test_int(child_v1->y, 5); + + const Velocity *child_v2 = ecs_get(world, child_2, Velocity); + test_assert(child_v2 != NULL); + test_int(child_v2->x, 4); + test_int(child_v2->y, 5); + test_assert(child_v1 != child_v2); + + ecs_fini(world); +} + +static int invoked; + +void PrefabReactiveTest(ecs_iter_t *it) { + invoked ++; +} + +void Prefab_ignore_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, PrefabReactiveTest, EcsOnAdd, Position); + + ECS_PREFAB(world, Prefab, Position); + + test_int(invoked, 0); + + ecs_fini(world); +} + +void Prefab_ignore_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, PrefabReactiveTest, EcsOnRemove, Position); + + ECS_PREFAB(world, Prefab, Position); + + test_int(invoked, 0); + + ecs_remove(world, Prefab, Position); + + test_int(invoked, 0); + + ecs_fini(world); +} + +void Prefab_ignore_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, PrefabReactiveTest, EcsOnSet, Position); + + ECS_PREFAB(world, Prefab, Position); + + test_int(invoked, 0); + + ecs_set(world, Prefab, Position, {0, 0}); + + test_int(invoked, 0); + + ecs_fini(world); +} + +void Prefab_on_set_on_instance() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, PrefabReactiveTest, EcsOnSet, ANY:Position); + + ECS_PREFAB(world, Prefab, Position); + + test_int(invoked, 0); + + ecs_set(world, Prefab, Position, {1, 2}); + + test_int(invoked, 0); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_int(invoked, 1); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void InstantiateInProgress(ecs_iter_t *it) { + ECS_COLUMN_ENTITY(it, Prefab, 2); + + ecs_entity_t *ids = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ids[i] = ecs_new_w_pair(it->world, EcsIsA, Prefab); + } +} + +void Prefab_instantiate_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Velocity); + ecs_set(world, Prefab, Velocity, {1, 2}); + + ECS_SYSTEM(world, InstantiateInProgress, EcsOnUpdate, Position, :Prefab); + + const ecs_entity_t *dummy_ids = ecs_bulk_new(world, Position, 10); + test_assert(dummy_ids != NULL); + + ecs_entity_t ids[10]; + ecs_set_context(world, ids); + + ecs_progress(world, 1); + + test_int(ecs_count_entity(world, ecs_pair(EcsIsA, Prefab)), 10); + + const Velocity *v_prefab = ecs_get(world, Prefab, Velocity); + + int i; + for (i = 0; i < 10; i ++) { + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_assert(v == v_prefab); + } + + ecs_fini(world); +} + +void NewInProgress(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Type, 2); + + ecs_entity_t *ids = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ids[i] = ecs_new(it->world, Type); + } +} + +void Prefab_copy_from_prefab_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Velocity); + ecs_set(world, Prefab, Velocity, {1, 2}); + + ECS_TYPE(world, Type, (IsA, Prefab), Velocity); + + ECS_SYSTEM(world, NewInProgress, EcsOnUpdate, Position, :Type); + + ecs_entity_t ids[10]; + ecs_set_context(world, ids); + + const ecs_entity_t *dummy_ids = ecs_bulk_new(world, Position, 10); + test_assert(dummy_ids != NULL); + + /* Create one prefab instance so table is already created (case where table + * is not created is tested in copy_from_prefab_first_instance_in_progress*/ + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_progress(world, 1); + + test_int(ecs_count_entity(world, ecs_pair(EcsIsA, Prefab)), 11); + + int i; + for (i = 0; i < 10; i ++) { + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + } + + ecs_fini(world); +} + +void Prefab_copy_from_prefab_first_instance_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Velocity); + ecs_set(world, Prefab, Velocity, {1, 2}); + + ECS_TYPE(world, Type, (IsA, Prefab), Velocity); + + ECS_SYSTEM(world, NewInProgress, EcsOnUpdate, Position, :Type); + + ecs_entity_t ids[10]; + ecs_set_context(world, ids); + + const ecs_entity_t *dummy_ids = ecs_bulk_new(world, Position, 10); + test_assert(dummy_ids != NULL); + + ecs_progress(world, 1); + + test_int(ecs_count_entity(world, ecs_pair(EcsIsA, Prefab)), 10); + + int i; + for (i = 0; i < 10; i ++) { + const Velocity *v = ecs_get(world, ids[i], Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + } + + ecs_fini(world); +} + +void Prefab_ref_after_realloc() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Mass); + ecs_set(world, Prefab, Mass, {2}); + + ECS_ENTITY(world, e1, Position, Mass); + ecs_set(world, e1, Mass, {3}); + + ECS_ENTITY(world, e2, Position, (IsA, Prefab)); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, ANY:Mass, Position); + + ecs_type_t prefab_type = ecs_get_type(world, Prefab); + + /* Trigger a realloc of the table in which the prefab is stored. This should + * cause systems with refs to re-resolve their cached ref ptrs */ + ecs_bulk_new_w_type(world, prefab_type, 1000); + + ecs_progress(world, 1); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 60); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Prefab_revalidate_ref_w_mixed_table_refs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Mass); + ecs_set(world, Prefab, Mass, {1}); + + ECS_ENTITY(world, e1, Position, (IsA, Prefab)); + ECS_ENTITY(world, e2, Position, Mass); + ecs_set(world, e2, Mass, {3}); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, ANY:Mass, Position); + + ecs_type_t prefab_type = ecs_get_type(world, Prefab); + + /* Trigger a realloc of the table in which the prefab is stored. This should + * cause systems with refs to re-resolve their cached ref ptrs */ + ecs_bulk_new_w_type(world, prefab_type, 1000); + + ecs_set(world, Prefab, Mass, {2}); + ecs_progress(world, 1); + + const Position *p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 60); + + p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Prefab_no_overwrite_on_2nd_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Mass); + ecs_set(world, Prefab, Mass, {1}); + + ECS_ENTITY(world, e1, Position, (IsA, Prefab)); + + ecs_add(world, e1, Mass); + test_int( *ecs_get(world, e1, Mass), 1); + + ecs_set(world, e1, Mass, {2}); + test_int( *ecs_get(world, e1, Mass), 2); + + ecs_add(world, e1, Mass); + test_int( *ecs_get(world, e1, Mass), 2); + + ecs_fini(world); +} + +void AddPrefab(ecs_iter_t *it) { + ECS_COLUMN_ENTITY(it, Prefab, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_pair(it->world, it->entities[i], EcsIsA, Prefab); + } +} + +void Prefab_no_overwrite_on_2nd_add_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Mass); + ecs_set(world, Prefab, Mass, {1}); + + ECS_SYSTEM(world, AddPrefab, EcsOnUpdate, Position, :Prefab); + + ECS_ENTITY(world, e1, Position, (IsA, Prefab)); + ecs_add(world, e1, Mass); + test_int( *ecs_get(world, e1, Mass), 1); + + ecs_set(world, e1, Mass, {2}); + test_int( *ecs_get(world, e1, Mass), 2); + + ecs_progress(world, 1); + + test_int( *ecs_get(world, e1, Mass), 2); + + ecs_fini(world); +} + +void Prefab_no_instantiate_on_2nd_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, (ChildOf, Prefab), Velocity); + ecs_set(world, ChildPrefab, Velocity, {3, 4}); + + ECS_SYSTEM(world, AddPrefab, EcsOnUpdate, Position, :Prefab); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert( ecs_has_pair(world, e, EcsIsA, Prefab)); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "ChildPrefab"); + test_assert(e_child != 0); + const Velocity *v = ecs_get(world, e_child, Velocity); + test_assert(v != NULL); + test_int(v->x, 3); + test_int(v->y, 4); + + ecs_set(world, e_child, Velocity, {4, 5}); + v = ecs_get(world, e_child, Velocity); + test_assert(v != NULL); + test_int(v->x, 4); + test_int(v->y, 5); + + ecs_add_pair(world, e, EcsIsA, Prefab); + + ecs_entity_t e_child_2 = ecs_lookup_child(world, e, "ChildPrefab"); + test_assert(e_child == e_child_2); + v = ecs_get(world, e_child, Velocity); + test_assert(v != NULL); + test_int(v->x, 4); + test_int(v->y, 5); + + ecs_fini(world); +} + +void Prefab_no_instantiate_on_2nd_add_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, (ChildOf, Prefab), Velocity); + ecs_set(world, ChildPrefab, Velocity, {3, 4}); + + ECS_SYSTEM(world, AddPrefab, EcsOnUpdate, Position, :Prefab); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert( ecs_has_pair(world, e, EcsIsA, Prefab)); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t e_child = ecs_lookup_child(world, e, "ChildPrefab"); + test_assert(e_child != 0); + const Velocity *v = ecs_get(world, e_child, Velocity); + test_assert(v != NULL); + test_int(v->x, 3); + test_int(v->y, 4); + + ecs_set(world, e_child, Velocity, {4, 5}); + v = ecs_get(world, e_child, Velocity); + test_assert(v != NULL); + test_int(v->x, 4); + test_int(v->y, 5); + + ecs_progress(world, 1); + + ecs_entity_t e_child_2 = ecs_lookup_child(world, e, "ChildPrefab"); + test_assert(e_child == e_child_2); + v = ecs_get(world, e_child, Velocity); + test_assert(v != NULL); + test_int(v->x, 4); + test_int(v->y, 5); + + ecs_fini(world); +} + +void NewPrefab_w_count(ecs_iter_t *it) { + ecs_entity_t *ids = ecs_get_context(it->world); + ECS_COLUMN_ENTITY(it, Prefab, 1); + const ecs_entity_t *new_ids = ecs_bulk_new_w_entity(it->world, ecs_pair(EcsIsA, Prefab), 3); + test_assert(new_ids != NULL); + memcpy(ids, new_ids, sizeof(ecs_entity_t) * 3); +} + +void Prefab_nested_prefab_in_progress_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, (ChildOf, Prefab), Velocity); + ecs_set(world, ChildPrefab, Velocity, {3, 4}); + + ECS_SYSTEM(world, NewPrefab_w_count, EcsOnUpdate, :Prefab); + + ecs_entity_t ids[3] = {0}; + ecs_set_context(world, ids); + + ecs_progress(world, 1); + + int i; + for (i = 0; i < 3; i ++) { + ecs_has_pair(world, ids[i], EcsIsA, Prefab); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t child = ecs_lookup_child(world, ids[i], "ChildPrefab"); + test_assert(child != 0); + + const Velocity *v = ecs_get(world, child, Velocity); + test_assert(v != NULL); + test_int(v->x, 3); + test_int(v->y, 4); + } + + ecs_fini(world); +} + +static int on_set_velocity_invoked; + +static +void OnSetVelocity(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + on_set_velocity_invoked ++; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + + if (ecs_is_owned(it, 1)) { + v[i].x ++; + v[i].y ++; + } + } +} + +void Prefab_nested_prefab_in_progress_w_count_set_after_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, (ChildOf, Prefab), Velocity); + ecs_set(world, ChildPrefab, Velocity, {3, 4}); + + ECS_SYSTEM(world, NewPrefab_w_count, EcsOnUpdate, :Prefab); + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, Velocity); + + ecs_entity_t ids[3] = {0}; + ecs_set_context(world, ids); + + ecs_progress(world, 1); + + test_assert(ids != NULL); + test_int(on_set_velocity_invoked, 3); + + int i; + for (i = 0; i < 3; i ++) { + ecs_has_pair(world, ids[i], EcsIsA, Prefab); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_entity_t child = ecs_lookup_child(world, ids[i], "ChildPrefab"); + test_assert(child != 0); + + const Velocity *v = ecs_get(world, child, Velocity); + test_assert(v != NULL); + test_int(v->x, 3 + 1); + test_int(v->y, 4 + 1); + } + + ecs_fini(world); +} + +void AddPrefabInProgress(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Prefab, 2); + ECS_COLUMN_COMPONENT(it, Velocity, 3); + + ecs_entity_t Prefab = ecs_type_to_entity(it->world, ecs_type(Prefab)); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_pair(it->world, it->entities[i], EcsIsA, Prefab); + test_assert( ecs_has(it->world, it->entities[i], Prefab)); + test_assert( ecs_has(it->world, it->entities[i], Velocity)); + + const Velocity *v = ecs_get(it->world, it->entities[i], Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + test_assert( ecs_get(it->world, Prefab, Velocity) == v); + } +} + +void Prefab_get_ptr_from_prefab_from_new_table_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Prefab, Velocity); + ecs_set(world, Prefab, Velocity, {1, 2}); + + ECS_SYSTEM(world, AddPrefabInProgress, EcsOnUpdate, Position, :Prefab, :Velocity); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + + ecs_progress(world, 1); + + test_assert( ecs_has_pair(world, e, EcsIsA, Prefab)); + test_assert( ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + test_assert( ecs_get(world, Prefab, Velocity) == v); + + ecs_fini(world); +} + +void TestBase(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + test_assert(!ecs_is_owned(it, 2)); + + test_assert(p != NULL); + test_assert(v != NULL); + test_int(it->count, 1); +} + +void Prefab_match_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_PREFAB(world, Base, Velocity); + ecs_set(world, Base, Velocity, {1, 2}); + + ECS_PREFAB(world, Child, (IsA, Base)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Child); + ecs_add(world, e, Position); + + ECS_SYSTEM(world, TestBase, EcsOnUpdate, Position, ANY:Velocity); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +static +void AddMass(ecs_iter_t *it) { + ECS_COLUMN_ENTITY(it, Mass, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Mass); + } +} + +void Prefab_match_base_after_add_in_prev_phase() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_PREFAB(world, Base, Velocity); + ecs_set(world, Base, Velocity, {1, 2}); + + ECS_PREFAB(world, Child, (IsA, Base)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Child); + ecs_add(world, e, Position); + + ECS_SYSTEM(world, AddMass, EcsPreUpdate, Position, !Mass); + ECS_SYSTEM(world, TestBase, EcsOnUpdate, Position, ANY:Velocity); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +void Prefab_override_watched_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + /* Create a system that listens for Position */ + ECS_SYSTEM(world, Dummy, EcsOnUpdate, ANY:Position); + + /* Create a prefab with Position */ + ECS_PREFAB(world, Prefab, Position); + + /* Create an instance of Prefab, this will cause Prefab to be watched since + * it will be matched as reference with the system */ + ECS_ENTITY(world, Entity1, (IsA, Prefab)); + + /* Another instance of Prefab is created, prefab data is resolved to check + * if components need to be overridden. Index will be negative, so code + * needs to flip sign on the index, or this will fail. */ + ECS_ENTITY(world, Entity2, (IsA, Prefab), Position); + + const Position *p1 = ecs_get(world, Prefab, Position); + const Position *p2 = ecs_get(world, Entity1, Position); + const Position *p3 = ecs_get(world, Entity2, Position); + + test_assert(p1 == p2); + test_assert(p2 != p3); + + ecs_fini(world); +} + +void Prefab_rematch_twice() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, ANY:Position, ANY:Velocity, ANY:Mass); + + ECS_PREFAB(world, Prefab, Position); + ECS_ENTITY(world, Entity, (IsA, Prefab)); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + test_int(ctx.count, 0); + ctx.count = 0; + + ecs_add(world, Prefab, Velocity); + + ecs_progress(world, 1); + test_int(ctx.count, 0); + ctx.count = 0; + + ecs_add(world, Prefab, Mass); + + ecs_progress(world, 1); + test_int(ctx.count, 1); + + ecs_fini(world); +} + +static +void AddPosition(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + ecs_entity_t *base = ecs_get_context(it->world); + + ecs_add(it->world, *base, Position); +} + +void Prefab_add_to_empty_base_in_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add_pair(world, e1, EcsIsA, base); + + ECS_SYSTEM(world, AddPosition, EcsOnUpdate, :Position); + + ecs_set_context(world, &base); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, base, Position)); + + ecs_fini(world); +} + +void Prefab_dont_inherit_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Velocity); + + ecs_add_id(world, e2, EcsDisabled); + ecs_add_pair(world, e1, EcsIsA, e2); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( !ecs_has_entity(world, e1, EcsDisabled)); + test_assert( ecs_has_pair(world, e1, EcsIsA, e2)); + test_assert( !ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has_entity(world, e2, EcsDisabled)); + + ecs_fini(world); +} + +static bool has_cloned = false; + +static +void CloneInOnAdd(ecs_iter_t *it) +{ + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + bool has_cloned_test = has_cloned; + + /* Prevent recursive OnAdd calls */ + has_cloned = true; + + p[i].x = 10; + p[i].y = 20; + + if (!has_cloned_test) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t clone = ecs_clone(it->world, 0, e, true); + ecs_add_pair(it->world, e, EcsIsA, clone); + } + } +} + +void Prefab_clone_after_inherit_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, CloneInOnAdd, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + test_assert(has_cloned == true); + + ecs_fini(world); +} + +void Prefab_override_from_nested() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, BasePrefab, Position); + ecs_set(world, BasePrefab, Position, {10, 20}); + + ECS_PREFAB(world, SubPrefab, (IsA, BasePrefab), Velocity); + ecs_set(world, SubPrefab, Velocity, {30, 40}); + + ECS_TYPE(world, Sub, (IsA, SubPrefab), Position, Velocity); + + ecs_entity_t e1 = ecs_new(world, Sub); + test_assert(e1 != 0); + test_assert( ecs_owns(world, e1, Position, true)); + test_assert( ecs_owns(world, e1, Velocity, true)); + test_assert( ecs_owns(world, e1, Position, true)); + test_assert( ecs_owns(world, e1, Velocity, true)); + test_assert( ecs_has_pair(world, e1, EcsIsA, SubPrefab)); + test_assert( ecs_has_pair(world, e1, EcsIsA, BasePrefab)); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_fini(world); +} + +static +void OnAddEntity(ecs_iter_t *it) { + ECS_COLUMN(it, ecs_entity_t, e, 1); + + int i; + for (i = 0; i < it->count; i ++) { + e[i] = it->entities[i]; + } +} + +void Prefab_create_multiple_nested_w_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, ecs_entity_t); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, Velocity); + + ECS_PREFAB(world, Child, (ChildOf, Prefab), Velocity, ecs_entity_t); + ecs_set(world, Child, Velocity, {3, 4}); + + ECS_SYSTEM(world, OnAddEntity, EcsOnSet, ecs_entity_t); + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, Velocity); + + test_int(on_set_velocity_invoked, 0); + + /* Create two entities with separate calls */ + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, Prefab); + ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, Prefab); + + test_int(on_set_velocity_invoked, 2); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + + ecs_entity_t child1 = ecs_lookup_child(world, e1, "Child"); + test_assert(child1 != 0); + + ecs_entity_t child2 = ecs_lookup_child(world, e2, "Child"); + test_assert(child2 != 0); + test_assert(child1 != child2); + + const ecs_entity_t *self1 = ecs_get(world, child1, ecs_entity_t); + test_assert(self1 != NULL); + test_assert(*self1 == child1); + + const ecs_entity_t *self2 = ecs_get(world, child2, ecs_entity_t); + test_assert(self2 != NULL); + test_assert(self1 != self2); + test_assert(*self2 == child2); + + test_assert( ecs_has(world, child1, Velocity)); + test_assert( ecs_has(world, child2, Velocity)); + test_assert( ecs_owns(world, child1, Velocity, true)); + test_assert( ecs_owns(world, child2, Velocity, true)); + + const Velocity *v1 = ecs_get(world, child1, Velocity); + test_assert(v1 != NULL); + test_int(v1->x, 4); + test_int(v1->y, 5); + + const Velocity *v2 = ecs_get(world, child2, Velocity); + test_assert(v2 != NULL); + test_assert(v1 != v2); + test_int(v2->x, 4); + test_int(v2->y, 5); + + ecs_fini(world); +} + +static ecs_entity_t new_instance_1, new_instance_2; + +static +void CreateInstances(ecs_iter_t *it) { + ECS_COLUMN_ENTITY(it, Prefab, 1); + + new_instance_1 = ecs_new_w_pair(it->world, EcsIsA, Prefab); + new_instance_2 = ecs_new_w_pair(it->world, EcsIsA, Prefab); +} + +void Prefab_create_multiple_nested_w_on_set_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, ecs_entity_t); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, Velocity); + ecs_set(world, ChildPrefab, Velocity, {3, 4}); + + ECS_PREFAB(world, Child, (ChildOf, Prefab), (IsA, ChildPrefab), ecs_entity_t); + + ECS_SYSTEM(world, CreateInstances, EcsOnUpdate, :Prefab); + ECS_SYSTEM(world, OnAddEntity, EcsOnSet, ecs_entity_t); + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, ANY:Velocity); + + ecs_progress(world, 1); + + test_int(on_set_velocity_invoked, 2); + + ecs_entity_t e1 = new_instance_1; + ecs_entity_t e2 = new_instance_2; + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + + ecs_entity_t child1 = ecs_lookup_child(world, e1, "Child"); + test_assert(child1 != 0); + + ecs_entity_t child2 = ecs_lookup_child(world, e2, "Child"); + test_assert(child2 != 0); + test_assert(child1 != child2); + + const ecs_entity_t *self1 = ecs_get(world, child1, ecs_entity_t); + test_assert(self1 != NULL); + test_assert(*self1 == child1); + + const ecs_entity_t *self2 = ecs_get(world, child2, ecs_entity_t); + test_assert(self2 != NULL); + test_assert(self1 != self2); + test_assert(*self2 == child2); + + test_assert( ecs_has(world, child1, Velocity)); + test_assert( ecs_has(world, child2, Velocity)); + test_assert( ecs_owns(world, child1, Velocity, true)); + test_assert( ecs_owns(world, child2, Velocity, true)); + + const Velocity *v1 = ecs_get(world, child1, Velocity); + test_assert(v1 != NULL); + test_int(v1->x, 3); + test_int(v1->y, 4); + + const Velocity *v2 = ecs_get(world, child2, Velocity); + test_assert(v2 != NULL); + test_assert(v1 != v2); + test_int(v2->x, 3); + test_int(v2->y, 4); + + ecs_fini(world); +} + +void Prefab_single_on_set_on_child_w_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, ecs_entity_t); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_PREFAB(world, ChildPrefab, Velocity); + ecs_set(world, ChildPrefab, Velocity, {3, 4}); + + ECS_PREFAB(world, Child, (ChildOf, Prefab), (IsA, ChildPrefab), Velocity); + + ECS_SYSTEM(world, OnSetVelocity, EcsOnSet, Velocity); + + test_int(on_set_velocity_invoked, 0); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + + /* Make sure OnSet is invoked only once, even though Child overrides a + * component from a ChildPrefab. Velocity is already overridden for Child, + * and while it would technically not be incorrect to issue an OnSet for the + * override, we don't want to spam the application with redundant triggers. */ + test_int(on_set_velocity_invoked, 1); + + test_assert( ecs_has(world, e, Position)); + + ecs_entity_t child = ecs_lookup_child(world, e, "Child"); + test_assert(child != 0); + + test_assert( ecs_has(world, child, Velocity)); + test_assert( ecs_owns(world, child, Velocity, true)); + + const Velocity *v = ecs_get(world, child, Velocity); + test_assert(v != NULL); + test_int(v->x, 4); + test_int(v->y, 5); + + ecs_fini(world); +} + +void Prefab_force_owned() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Position, Velocity, OWNED | Position); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_owns(world, e, Position, true)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(!ecs_owns(world, e, Velocity, true)); + + ecs_fini(world); +} + +void Prefab_force_owned_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Position, Velocity, OWNED | Position, OWNED | Velocity); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_owns(world, e, Position, true)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_owns(world, e, Velocity, true)); + + ecs_fini(world); +} + +void Prefab_force_owned_nested() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Position, Velocity, OWNED | Position); + ECS_PREFAB(world, Prefab_2, (IsA, Prefab)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab_2); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_owns(world, e, Position, true)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(!ecs_owns(world, e, Velocity, true)); + + ecs_fini(world); +} + +void Prefab_force_owned_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Rotation); + + ECS_PREFAB(world, Prefab, Position, Rotation, Velocity, OWNED | Type); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_owns(world, e, Position, true)); + test_assert(ecs_has(world, e, Rotation)); + test_assert(ecs_owns(world, e, Rotation, true)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(!ecs_owns(world, e, Velocity, true)); + + ecs_fini(world); +} + +void Prefab_force_owned_type_w_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, (Position, Velocity)); + + ECS_PREFAB(world, Prefab, (Position, Velocity), OWNED | Type); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + + ecs_entity_t pair = ecs_pair(ecs_id(Position), ecs_id(Velocity)); + test_assert(ecs_has_entity(world, e, pair)); + test_assert(ecs_owns_entity(world, e, pair, true)); + + ecs_fini(world); +} + +void Prefab_prefab_instanceof_hierarchy() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Base, Position); + ECS_PREFAB(world, BaseChild, Position, (ChildOf, Base)); + + ECS_PREFAB(world, Prefab, (IsA, Base)); + + /* Ensure that child has been instantiated for Prefab as a prefab by making + * sure there are no matching entities for Position up to this point */ + ecs_query_t *q = ecs_query_new(world, "ANY:Position"); + + ecs_iter_t qit = ecs_query_iter(q); + test_assert(!ecs_query_next(&qit)); + + /* Instantiate the prefab */ + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e != 0); + + qit = ecs_query_iter(q); + test_assert(ecs_query_next(&qit) == true); + test_int(qit.count, 1); + test_assert(ecs_query_next(&qit) == true); + test_int(qit.count, 1); + test_assert(ecs_query_next(&qit) == false); + + ecs_fini(world); +} + +void Prefab_override_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_entity_t base = ecs_new(world, Tag); + ecs_set(world, base, Position, {10, 20}); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Tag)); + + ecs_add(world, e, Tag); + test_assert(ecs_has(world, e, Tag)); + test_assert(ecs_owns(world, e, Tag, true)); + + ecs_fini(world); +} + +void Prefab_empty_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has_pair(world, e, EcsIsA, base)); + + ecs_fini(world); +} + +void Prefab_instanceof_0() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + install_test_abort(); + + test_expect_abort(); + + /* Not allowed */ + ecs_new_w_pair(world, EcsIsA, 0); + + ecs_fini(world); +} + +void Prefab_instantiate_empty_child_table() { + ecs_world_t *world = ecs_init(); + + ECS_PREFAB(world, Prefab, 0); + + /* Forces creation of child table without children */ + ecs_table_t *table = ecs_table_from_str(world, "(ChildOf, Prefab)"); + test_assert(table != NULL); + + /* Main goal of this test is to ensure this doesn't crash */ + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, EcsIsA, Prefab)); + + /* This shouldn't crash either */ + ecs_delete(world, Prefab); + test_assert(!ecs_is_alive(world, Prefab)); + + ecs_fini(world); +} + +void Prefab_instantiate_emptied_child_table() { + ecs_world_t *world = ecs_init(); + + ECS_PREFAB(world, Prefab, 0); + + /* Create & remove prefab child */ + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, Prefab); + test_assert(child != 0); + + /* Deleting the child will leave an initialized but empty table */ + ecs_delete(world, child); + test_assert(!ecs_is_alive(world, child)); + + /* Main goal of this test is to ensure this doesn't crash */ + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e != 0); + test_assert(ecs_has_pair(world, e, EcsIsA, Prefab)); + + /* This shouldn't crash either */ + ecs_delete(world, Prefab); + test_assert(!ecs_is_alive(world, Prefab)); + + ecs_fini(world); +} + +void Prefab_override_2_prefabs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, PrefabA, Position); + ECS_PREFAB(world, PrefabB, Velocity); + + ecs_set(world, PrefabA, Position, {10, 20}); + ecs_set(world, PrefabB, Velocity, {1, 2}); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_pair(world, e, EcsIsA, PrefabA); + ecs_add_pair(world, e, EcsIsA, PrefabB); + + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + test_assert(ecs_owns(world, e, Position, true)); + test_assert(ecs_owns(world, e, Velocity, true)); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e, Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_fini(world); +} + +void Prefab_rematch_after_add_instanceof_to_parent() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_new(world, "PARENT:Position"); + test_assert(q != NULL); + + ecs_entity_t base = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t parent = ecs_new(world, 0); + ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); + test_assert(base != 0); + test_assert(parent != 0); + test_assert(child != 0); + + ecs_add_pair(world, parent, EcsIsA, base); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + + test_int(it.count, 1); + test_int(it.entities[0], child); + + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Prefab_child_of_instance() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_new_id(world); + ecs_entity_t e = ecs_new_id(world); + + ecs_add_pair(world, e, EcsChildOf, base); + + test_expect_abort(); + + /* Should trigger an assert */ + ecs_add_pair(world, e, EcsIsA, base); + + ecs_fini(world); +} + +void Prefab_rematch_after_prefab_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + + ecs_query_t *q = ecs_query_new(world, "SHARED:Position"); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e); + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_delete(world, base); + + it = ecs_query_iter(q); + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Prefab_add_tag_w_low_id_to_instance() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t Tag = ecs_new_component_id(world); + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_set(world, 0, Position, {10, 20}); + ecs_add_id(world, base, Tag); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + ecs_add(world, e, Position); + ecs_add_id(world, e, Tag); + + test_assert(ecs_has_entity(world, e, Tag)); + + ecs_fini(world); +} + +void Prefab_get_type_after_base_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, Position); + test_assert(base != 0); + test_assert( ecs_get_type(world, base) != NULL); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_get_type(world, base) != NULL); + + ecs_fini(world); +} + +void Prefab_get_type_after_recycled_base_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + test_assert( ecs_get_type(world, base) == NULL); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_new(world, Position); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + test_assert( ecs_get_type(world, base) != NULL); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_get_type(world, base) != NULL); + + ecs_fini(world); +} + +void Prefab_new_w_recycled_base() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_new(world, 0); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + + ecs_fini(world); +} + +void Prefab_add_recycled_base() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_new(world, 0); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + ecs_add_pair(world, e, EcsIsA, base); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + + ecs_fini(world); +} + +void Prefab_remove_recycled_base() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_new(world, 0); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + ecs_add_pair(world, e, EcsIsA, base); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + + ecs_remove_pair(world, e, EcsIsA, base); + test_assert( !ecs_has_pair(world, e, EcsIsA, base)); + + ecs_fini(world); +} + +void Prefab_get_from_recycled_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_set(world, 0, Position, {10, 20}); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + test_assert( ecs_get_type(world, base) != NULL); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_owns(world, e, Position, true)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + test_assert(p == ecs_get(world, base, Position)); + + ecs_fini(world); +} + +void Prefab_override_from_recycled_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_set(world, 0, Position, {10, 20}); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_owns(world, e, Position, true)); + + ecs_add(world, e, Position); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_owns(world, e, Position, true)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Prefab_remove_override_from_recycled_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_set(world, 0, Position, {10, 20}); + test_assert(base != 0); + test_assert(ecs_entity_t_lo(base) != base); // Ensure recycled + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_owns(world, e, Position, true)); + + ecs_add(world, e, Position); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_owns(world, e, Position, true)); + + ecs_remove(world, e, Position); + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_owns(world, e, Position, true)); + + ecs_fini(world); +} + +void Prefab_instantiate_tree_from_recycled_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_set(world, 0, Position, {10, 20}); + test_assert(base != 0); + + ecs_entity_t base_child = ecs_new_w_pair(world, EcsChildOf, base); + test_assert(base_child != 0); + + ecs_set_name(world, base_child, "Child"); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_owns(world, e, Position, true)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + test_assert(p == ecs_get(world, base, Position)); + + ecs_entity_t instance_child = ecs_lookup_child(world, e, "Child"); + test_assert(instance_child != 0); + test_assert( ecs_has_pair(world, instance_child, EcsChildOf, e)); + + ecs_fini(world); +} + +void Prefab_rematch_after_add_to_recycled_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_new(world, "Tag, SHARED:Position"); + + ecs_entity_t base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_delete(world, base); + test_assert( !ecs_is_alive(world, base)); + + base = ecs_new(world, 0); + test_assert(base != 0); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + test_assert(e != 0); + test_assert( ecs_has_pair(world, e, EcsIsA, base)); + ecs_add(world, e, Tag); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), false); + + ecs_set(world, base, Position, {10, 20}); + + ecs_progress(world, 0); + + it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + + const Position *p = ecs_term(&it, Position, 2); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + test_assert(ecs_term_source(&it, 2) == base); + + ecs_fini(world); +} + +void Prefab_get_tag_from_2nd_base() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t base_1 = ecs_new_id(world); + ecs_entity_t base_2 = ecs_new_w_id(world, Tag); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, EcsIsA, base_1); + ecs_add_pair(world, e, EcsIsA, base_2); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has_id(world, e, Tag)); + + ecs_fini(world); +} + +void Prefab_get_component_from_2nd_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base_1 = ecs_new_id(world); + ecs_entity_t base_2 = ecs_new(world, Position); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, EcsIsA, base_1); + ecs_add_pair(world, e, EcsIsA, base_2); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_2, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_1st_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base_1 = ecs_new(world, Position); + ecs_entity_t base_2 = ecs_new(world, Position); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, EcsIsA, base_1); + ecs_add_pair(world, e, EcsIsA, base_2); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_1, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_2nd_base_of_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base_1 = ecs_new_id(world); + ecs_entity_t base_2 = ecs_new(world, Position); + + ecs_entity_t base_3 = ecs_new_id(world); + ecs_add_pair(world, base_3, EcsIsA, base_1); + ecs_add_pair(world, base_3, EcsIsA, base_2); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_3); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_2, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_1st_base_of_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base_1 = ecs_new(world, Position); + ecs_entity_t base_2 = ecs_new(world, Position); + + ecs_entity_t base_3 = ecs_new_id(world); + ecs_add_pair(world, base_3, EcsIsA, base_1); + ecs_add_pair(world, base_3, EcsIsA, base_2); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_3); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_1, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_2nd_base_prefab_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, base_1, 0); + ECS_PREFAB(world, base_2, Position); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, EcsIsA, base_1); + ecs_add_pair(world, e, EcsIsA, base_2); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_2, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_1st_base_prefab_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, base_1, Position); + ECS_PREFAB(world, base_2, Position); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_pair(world, e, EcsIsA, base_1); + ecs_add_pair(world, e, EcsIsA, base_2); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_1, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_2nd_base_of_base_prefab_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, base_1, 0); + ECS_PREFAB(world, base_2, Position); + ECS_PREFAB(world, base_3, (IsA, base_1), (IsA, base_2)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_3); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_2, Position)); + + ecs_fini(world); +} + +void Prefab_get_component_from_1st_base_of_base_prefab_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, base_1, Position); + ECS_PREFAB(world, base_2, Position); + ECS_PREFAB(world, base_3, (IsA, base_1), (IsA, base_2)); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_3); + + test_assert(ecs_has_pair(world, e, EcsIsA, base_1)); + test_assert(ecs_has_pair(world, e, EcsIsA, base_2)); + + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, base_1, Position)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Query.c b/fggl/ecs2/flecs/test/api/src/Query.c new file mode 100644 index 0000000000000000000000000000000000000000..f0697aef2c6c122ffe0a3aeecb40fc71428bf1e0 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Query.c @@ -0,0 +1,1958 @@ +#include <api.h> + +void Query_query_changed_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_new(world, Position); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_query_changed_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_delete(world, e1); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_query_changed_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, 0); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_add(world, e1, Position); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_query_changed_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_remove(world, e1, Position); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_query_changed_after_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_set(world, e1, Position, {10, 20}); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_query_change_after_modified() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_modified(world, e1, Position); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Sys(ecs_iter_t *it) { } + +void Query_query_change_after_out_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Sys, EcsOnUpdate, [out] Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_progress(world, 0); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_query_change_after_in_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Sys, EcsOnUpdate, [in] Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + test_assert(ecs_query_changed(q) == true); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_progress(world, 0); + test_assert(ecs_query_changed(q) == false); + + ecs_query_iter(q); + test_assert(ecs_query_changed(q) == false); + + ecs_fini(world); +} + +void Query_subquery_match_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_bulk_new(world, Position, 3); + ecs_bulk_new(world, Velocity, 3); + ecs_bulk_new(world, Type, 3); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity"); + test_assert(sq != NULL); + + int32_t table_count = 0, entity_count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + } + } + + test_int(table_count, 2); + test_int(entity_count, 6); + + table_count = 0, entity_count = 0; + it = ecs_query_iter(sq); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + test_assert(ecs_has(world, it.entities[i], Velocity)); + } + } + + test_int(table_count, 1); + test_int(entity_count, 3); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_subquery_match_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity"); + test_assert(sq != NULL); + + ECS_TYPE(world, Type, Position, Velocity); + ecs_bulk_new(world, Position, 3); + ecs_bulk_new(world, Velocity, 3); + ecs_bulk_new(world, Type, 3); + + int32_t table_count = 0, entity_count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + } + } + + test_int(table_count, 2); + test_int(entity_count, 6); + + table_count = 0, entity_count = 0; + it = ecs_query_iter(sq); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + test_assert(ecs_has(world, it.entities[i], Velocity)); + } + } + + test_int(table_count, 1); + test_int(entity_count, 3); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_subquery_inactive() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TYPE(world, Type, Position, Velocity); + ecs_bulk_new(world, Position, 3); + ecs_bulk_new(world, Velocity, 3); + ecs_entity_t e = ecs_new(world, Type); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity"); + test_assert(sq != NULL); + + /* Create an empty table which should deactivate it for both queries */ + ecs_delete(world, e); + + int32_t table_count = 0, entity_count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + } + } + + test_int(table_count, 1); + test_int(entity_count, 3); + + table_count = 0, entity_count = 0; + it = ecs_query_iter(sq); + test_int(it.table_count, 0); + test_int(it.inactive_table_count, 1); + + table_count = 0, entity_count = 0; + it = ecs_query_iter(sq); + while (ecs_query_next(&it)) { + table_count ++; + } + + test_int(table_count, 0); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_subquery_unmatch() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add(world, parent, Position); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + ecs_add_pair(world, e1, EcsChildOf, parent); + + ecs_entity_t e2 = ecs_new(world, 0); + ecs_add(world, e2, Position); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_query_t *q = ecs_query_new(world, "Position, PARENT:Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity"); + test_assert(sq != NULL); + + int32_t table_count = 0, entity_count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + } + } + + test_int(table_count, 2); + test_int(entity_count, 2); + + table_count = 0, entity_count = 0; + it = ecs_query_iter(sq); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + test_assert(ecs_has(world, it.entities[i], Velocity)); + } + } + + test_int(table_count, 1); + test_int(entity_count, 1); + + /* Query now no longer match */ + ecs_remove(world, parent, Position); + + /* Force rematching */ + ecs_progress(world, 0); + + /* Test if tables have been unmatched */ + it = ecs_query_iter(q); + test_int(it.table_count, 0); + + it = ecs_query_iter(sq); + test_int(it.table_count, 0); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_subquery_rematch() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add(world, parent, Position); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + ecs_add_pair(world, e1, EcsChildOf, parent); + + ecs_entity_t e2 = ecs_new(world, 0); + ecs_add(world, e2, Position); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_query_t *q = ecs_query_new(world, "Position, PARENT:Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity"); + test_assert(sq != NULL); + + int32_t table_count = 0, entity_count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + } + } + + test_int(table_count, 2); + test_int(entity_count, 2); + + table_count = 0, entity_count = 0; + it = ecs_query_iter(sq); + while (ecs_query_next(&it)) { + table_count ++; + entity_count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(ecs_has(world, it.entities[i], Position)); + test_assert(ecs_has(world, it.entities[i], Velocity)); + } + } + + test_int(table_count, 1); + test_int(entity_count, 1); + + /* Query now no longer match */ + ecs_remove(world, parent, Position); + + /* Force unmatching */ + ecs_progress(world, 0); + + /* Test if tables have been unmatched */ + it = ecs_query_iter(q); + test_int(it.table_count, 0); + + it = ecs_query_iter(sq); + test_int(it.table_count, 0); + + /* Rematch queries */ + ecs_add(world, parent, Position); + + /* Force rematching */ + ecs_progress(world, 0); + + /* Test if tables have been rematched */ + it = ecs_query_iter(q); + test_int(it.table_count, 2); + + it = ecs_query_iter(sq); + test_int(it.table_count, 1); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_subquery_rematch_w_parent_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t parent = ecs_new(world, 0); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + ecs_add_pair(world, e1, EcsChildOf, parent); + + ecs_entity_t e2 = ecs_new(world, 0); + ecs_add(world, e2, Position); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_query_t *q = ecs_query_new(world, "Position, ?PARENT:Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity"); + test_assert(sq != NULL); + + ecs_iter_t it = ecs_query_iter(q); + test_int(it.table_count, 2); + + it = ecs_query_iter(sq); + test_int(it.table_count, 1); + + /* Trigger rematch */ + ecs_add(world, parent, Position); + ecs_progress(world, 0); + + /* Tables should be matched */ + it = ecs_query_iter(q); + test_int(it.table_count, 3); + + it = ecs_query_iter(sq); + test_int(it.table_count, 1); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_subquery_rematch_w_sub_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t parent = ecs_new(world, 0); + // ecs_add(world, parent, Position); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + ecs_add_pair(world, e1, EcsChildOf, parent); + + ecs_entity_t e2 = ecs_new(world, 0); + ecs_add(world, e2, Position); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_query_t *q = ecs_query_new(world, "Position, ?PARENT:Position"); + test_assert(q != NULL); + + ecs_query_t *sq = ecs_subquery_new(world, q, "Velocity, ?Mass"); + test_assert(sq != NULL); + + ecs_iter_t it = ecs_query_iter(q); + test_int(it.table_count, 2); + + it = ecs_query_iter(sq); + test_int(it.table_count, 1); + + /* Trigger rematch */ + ecs_add(world, parent, Position); + ecs_progress(world, 0); + + /* Tables should be matched */ + it = ecs_query_iter(q); + test_int(it.table_count, 3); + + it = ecs_query_iter(sq); + test_int(it.table_count, 1); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_query_single_pairs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, Rel); + + ECS_ENTITY(world, e1, (Rel, Position)); + ECS_ENTITY(world, e2, (Rel, Velocity)); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Velocity); + + int32_t table_count = 0, entity_count = 0; + + ecs_query_t *q = ecs_query_new(world, "(Rel, Velocity)"); + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e2); + entity_count ++; + } + } + + test_int(table_count, 1); + test_int(entity_count, 1); + + ecs_fini(world); +} + +void Query_query_single_instanceof() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, BaseA, 0); + ECS_ENTITY(world, BaseB, 0); + ECS_ENTITY(world, e1, (IsA, BaseB)); + ECS_ENTITY(world, e2, (IsA, BaseA)); + + int32_t table_count = 0, entity_count = 0; + + ecs_query_t *q = ecs_query_new(world, "(IsA, BaseA)"); + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e2); + entity_count ++; + } + } + + test_int(table_count, 1); + test_int(entity_count, 1); + + ecs_fini(world); +} + +void Query_query_single_childof() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, BaseA, 0); + ECS_ENTITY(world, BaseB, 0); + ECS_ENTITY(world, e1, (ChildOf, BaseB)); + ECS_ENTITY(world, e2, (ChildOf, BaseA)); + + int32_t table_count = 0, entity_count = 0; + + ecs_query_t *q = ecs_query_new(world, "(ChildOf, BaseA)"); + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + table_count ++; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(it.entities[i] == e2); + entity_count ++; + } + } + + test_int(table_count, 1); + test_int(entity_count, 1); + + ecs_fini(world); +} + +void Query_query_w_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_new(world, Position); + ecs_add(world, ecs_new(world, + Position), + Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_filter_t f = { + .include = ecs_type(Velocity) + }; + + ecs_iter_t it = ecs_query_iter(q); + int32_t table_count = 0, entity_count = 0; + while (ecs_query_next_w_filter(&it, &f)) { + table_count ++; + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + entity_count ++; + } + } + + test_int(table_count, 1); + test_int(entity_count, 1); + + ecs_fini(world); +} + +void Query_query_optional_owned() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t base = ecs_new(world, Velocity); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add_pair(world, e1, EcsIsA, base); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position, ?Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + test_int(it.count, 1); + test_assert(p != NULL); + + if (it.entities[0] == e1) { + test_assert(v == NULL); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + } else if (it.entities[0] == e2) { + test_assert(v != NULL); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), true); + } else if (it.entities[0] == e3) { + test_assert(v == NULL); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + } + + count ++; + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Query_query_optional_shared() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t base = ecs_new(world, Velocity); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add_pair(world, e1, EcsIsA, base); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position, ?SHARED:Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + test_int(it.count, 1); + test_assert(p != NULL); + + if (it.entities[0] == e1) { + test_assert(v != NULL); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), true); + } else if (it.entities[0] == e2) { + test_assert(v == NULL); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + } else if (it.entities[0] == e3) { + test_assert(v == NULL); + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + } + + count ++; + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Query_query_optional_shared_nested() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t base_base = ecs_new(world, Velocity); + ecs_entity_t base = ecs_new(world, 0); + ecs_add_pair(world, base, EcsIsA, base_base); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add_pair(world, e1, EcsIsA, base); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position, ?SHARED:Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + test_int(it.count, 1); + test_assert(p != NULL); + + if (it.entities[0] == e1) { + test_assert(v != NULL); + } else if (it.entities[0] == e2) { + test_assert(v == NULL); + } else if (it.entities[0] == e3) { + test_assert(v == NULL); + } + + count ++; + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Query_query_optional_any() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t base = ecs_new(world, Velocity); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add_pair(world, e1, EcsIsA, base); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position, ?ANY:Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + test_int(it.count, 1); + test_assert(p != NULL); + + if (it.entities[0] == e1) { + test_assert(v != NULL); + test_assert(!ecs_is_owned(&it, 2)); + } else if (it.entities[0] == e2) { + test_assert(v != NULL); + test_assert(ecs_is_owned(&it, 2)); + } else if (it.entities[0] == e3) { + test_assert(v == NULL); + } + + count ++; + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Query_query_rematch_optional_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t base = ecs_new(world, 0); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add_pair(world, e1, EcsIsA, base); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position, ?SHARED:Velocity"); + test_assert(q != NULL); + + /* First iteration, base doesn't have Velocity but query should match with + * entity anyway since the component is optional */ + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + test_int(it.count, 1); + test_assert(p != NULL); + + if (it.entities[0] == e1) { + test_assert(v == NULL); + } else if (it.entities[0] == e2) { + test_assert(v == NULL); + } else if (it.entities[0] == e3) { + test_assert(v == NULL); + } + + count ++; + } + test_int(count, 3); + + /* Add Velocity to base, should trigger rematch */ + ecs_add(world, base, Velocity); + + /* Trigger a merge, which triggers the rematch */ + ecs_staging_begin(world); + ecs_staging_end(world); + + /* Second iteration, base has Velocity and entity should be able to access + * the shared component. */ + it = ecs_query_iter(q); + count = 0; + + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + Velocity *v = ecs_term(&it, Velocity, 2); + + test_int(it.count, 1); + test_assert(p != NULL); + + if (it.entities[0] == e1) { + test_assert(v != NULL); + } else if (it.entities[0] == e2) { + test_assert(v == NULL); + } else if (it.entities[0] == e3) { + test_assert(v == NULL); + } + + count ++; + } + test_int(count, 3); + + ecs_fini(world); +} + +void Query_get_owned_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_new(world, Tag); + + ecs_query_t *q = ecs_query_new(world, "Tag"); + + int count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + test_assert(ecs_term_w_size(&it, 0, 1) == NULL); + test_int(it.count, 1); + test_int(it.entities[0], e); + count += it.count; + } + + test_int(count, 1); + + ecs_fini(world); +} + +void Query_get_shared_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t base = ecs_new(world, Tag); + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base); + + ecs_query_t *q = ecs_query_new(world, "SHARED:Tag"); + + int count = 0; + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + test_assert(ecs_term_w_size(&it, 0, 1) == NULL); + test_int(it.count, 1); + test_int(it.entities[0], instance); + count += it.count; + } + + test_int(count, 1); + + ecs_fini(world); +} + +void Query_explicit_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + /* Ensure query isn't deleted twice when deleting world */ + ecs_query_free(q); + + ecs_fini(world); +} + +void Query_get_column_size() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(ecs_term_size(&it, 1), sizeof(Position)); + + ecs_fini(world); +} + +void Query_orphaned_query() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + /* Nonsense subquery, doesn't matter, this is just for orphan testing */ + ecs_query_t *sq = ecs_subquery_new(world, q, "Position"); + test_assert(sq != NULL); + + test_assert(!ecs_query_orphaned(sq)); + + ecs_query_free(q); + + test_assert(ecs_query_orphaned(sq)); + + ecs_query_free(sq); + + ecs_fini(world); +} + +void Query_nested_orphaned_query() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + /* Nonsense subquery, doesn't matter, this is just for orphan testing */ + ecs_query_t *sq = ecs_subquery_new(world, q, "Position"); + test_assert(sq != NULL); + + ecs_query_t *nsq = ecs_subquery_new(world, sq, "Position"); + test_assert(nsq != NULL); + + test_assert(!ecs_query_orphaned(sq)); + test_assert(!ecs_query_orphaned(nsq)); + + ecs_query_free(q); + + test_assert(ecs_query_orphaned(sq)); + test_assert(ecs_query_orphaned(nsq)); + + ecs_query_free(sq); + ecs_query_free(nsq); + + ecs_fini(world); +} + +void Query_invalid_access_orphaned_query() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + /* Nonsense subquery, doesn't matter, this is just for orphan testing */ + ecs_query_t *sq = ecs_subquery_new(world, q, "Position"); + test_assert(sq != NULL); + + test_assert(!ecs_query_orphaned(sq)); + + ecs_query_free(q); + + test_expect_abort(); + + ecs_query_iter(sq); +} + +void Query_stresstest_query_free() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + ECS_TAG(world, Hello); + + ecs_entity_t e = ecs_new_id(world); + ecs_add(world, e, Foo); + ecs_add(world, e, Bar); + ecs_add(world, e, Hello); + + /* Create & delete query to test if query is properly unregistered with + * the table */ + + for (int i = 0; i < 10000; i ++) { + ecs_query_t *q = ecs_query_new(world, "Foo, Bar, Hello"); + ecs_query_free(q); + } + + /* If code did not crash, test passes */ + test_assert(true); + + ecs_fini(world); +} + +void Query_only_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_set_name(world, 0, "e"); + + ecs_query_t *q = ecs_query_new(world, "e:Tag"); + ecs_iter_t it = ecs_query_iter(q); + test_assert(!ecs_query_next(&it)); + + ecs_add(world, e, Tag); + + it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_assert(ecs_term_source(&it, 1) == e); + test_assert(ecs_term_id(&it, 1) == Tag); + + ecs_fini(world); +} + +void Query_only_from_singleton() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_set_name(world, 0, "e"); + + ecs_query_t *q = ecs_query_new(world, "$e"); + ecs_iter_t it = ecs_query_iter(q); + test_assert(!ecs_query_next(&it)); + + ecs_add_id(world, e, e); + + it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_assert(ecs_term_source(&it, 1) == e); + test_assert(ecs_term_id(&it, 1) == e); + + ecs_fini(world); +} + +void Query_only_not_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_set_name(world, 0, "e"); + + ecs_query_t *q = ecs_query_new(world, "!e:Tag"); + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_assert(ecs_term_source(&it, 1) == e); + test_assert(ecs_term_id(&it, 1) == Tag); + + ecs_add(world, e, Tag); + + it = ecs_query_iter(q); + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Query_only_not_from_singleton() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_set_name(world, 0, "e"); + + ecs_query_t *q = ecs_query_new(world, "!$e"); + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_assert(ecs_term_source(&it, 1) == e); + test_assert(ecs_term_id(&it, 1) == e); + + ecs_add_id(world, e, e); + + it = ecs_query_iter(q); + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Query_get_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + test_assert(q != NULL); + + const ecs_filter_t *f = ecs_query_get_filter(q); + test_assert(f != NULL); + + test_int(f->term_count, 2); + test_int(f->terms[0].id, ecs_id(Position)); + test_int(f->terms[1].id, ecs_id(Velocity)); + + ecs_fini(world); +} + +static +int32_t group_by_first_id( + ecs_world_t *world, + ecs_type_t type, + ecs_id_t id, + void *ctx) +{ + ecs_id_t *second = ecs_vector_get(type, ecs_id_t, 1); + if (!second) { + return 0; + } + + return second[0]; +} + +void Query_group_by() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter.terms = {{ecs_id(Position)}}, + .group_by = group_by_first_id + }); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_add_id(world, e1, TagC); + ecs_add_id(world, e2, TagB); + ecs_add_id(world, e3, TagA); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e2); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + + test_bool(ecs_query_next(&it), false); + + ecs_fini(world); +} + +static int ctx_value; +static int ctx_value_free_invoked = 0; +static +void ctx_value_free(void *ctx) { + test_assert(ctx == &ctx_value); + ctx_value_free_invoked ++; +} + +void Query_group_by_w_ctx() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter.terms = {{ecs_id(Position)}}, + .group_by = group_by_first_id, + .group_by_ctx = &ctx_value, + .group_by_ctx_free = ctx_value_free + }); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_add_id(world, e1, TagC); + ecs_add_id(world, e2, TagB); + ecs_add_id(world, e3, TagA); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e3); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e2); + + test_bool(ecs_query_next(&it), true); + test_int(it.count, 1); + test_int(it.entities[0], e1); + + test_bool(ecs_query_next(&it), false); + + ecs_query_fini(q); + + test_int(ctx_value_free_invoked, 1); + + ecs_fini(world); +} + +void Query_iter_valid() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + test_bool(it.is_valid, false); + + test_bool(ecs_query_next(&it), true); + test_bool(it.is_valid, true); + + test_bool(ecs_query_next(&it), false); + test_bool(it.is_valid, false); + + ecs_fini(world); +} + +void Query_query_optional_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e1 = ecs_new(world, TagA); + ecs_entity_t e2 = ecs_new(world, TagA); + ecs_add_id(world, e2, TagB); + + ecs_query_t *q = ecs_query_new(world, "TagA, ?TagB"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + test_assert(ecs_term_id(&it, 1) == TagA); + test_assert(ecs_term_id(&it, 2) == TagB); + test_int(it.count, 1); + + if (it.entities[0] == e1) { + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + } else if (it.entities[0] == e2) { + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), true); + } + + count ++; + } + + test_int(count, 2); + + ecs_fini(world); +} + +void Query_query_optional_shared_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e1 = ecs_new(world, TagA); + ecs_entity_t e2 = ecs_new(world, TagA); + ecs_add_id(world, e2, TagB); + + ecs_entity_t e3 = ecs_new(world, TagA); + ecs_add_pair(world, e3, EcsIsA, e2); + + ecs_query_t *q = ecs_query_new(world, "TagA, ?TagB(self|superset)"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + + while (ecs_query_next(&it)) { + test_assert(ecs_term_id(&it, 1) == TagA); + test_assert(ecs_term_id(&it, 2) == TagB); + test_int(it.count, 1); + + if (it.entities[0] == e1) { + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), false); + } else if (it.entities[0] == e2) { + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), true); + } else if (it.entities[0] == e3) { + test_bool(ecs_term_is_set(&it, 1), true); + test_bool(ecs_term_is_set(&it, 2), true); + } + + count ++; + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Query_query_iter_10_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + ECS_TAG(world, TagK); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_add_id(world, e_1, TagB); + ecs_add_id(world, e_1, TagC); + ecs_add_id(world, e_1, TagD); + ecs_add_id(world, e_1, TagE); + ecs_add_id(world, e_1, TagF); + ecs_add_id(world, e_1, TagG); + ecs_add_id(world, e_1, TagH); + ecs_add_id(world, e_1, TagI); + ecs_add_id(world, e_1, TagJ); + + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_add_id(world, e_2, TagB); + ecs_add_id(world, e_2, TagC); + ecs_add_id(world, e_2, TagD); + ecs_add_id(world, e_2, TagE); + ecs_add_id(world, e_2, TagF); + ecs_add_id(world, e_2, TagG); + ecs_add_id(world, e_2, TagH); + ecs_add_id(world, e_2, TagI); + ecs_add_id(world, e_2, TagJ); + ecs_add_id(world, e_2, TagK); /* 2nd match in different table */ + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter.terms = { + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ} + } + }); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + + test_assert(!ecs_query_next(&it)); + + ecs_query_fini(q); + + ecs_fini(world); +} + +void Query_query_iter_20_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + ECS_TAG(world, TagD); + ECS_TAG(world, TagE); + ECS_TAG(world, TagF); + ECS_TAG(world, TagG); + ECS_TAG(world, TagH); + ECS_TAG(world, TagI); + ECS_TAG(world, TagJ); + ECS_TAG(world, TagK); + ECS_TAG(world, TagL); + ECS_TAG(world, TagM); + ECS_TAG(world, TagN); + ECS_TAG(world, TagO); + ECS_TAG(world, TagP); + ECS_TAG(world, TagQ); + ECS_TAG(world, TagR); + ECS_TAG(world, TagS); + ECS_TAG(world, TagT); + ECS_TAG(world, TagU); + + ecs_entity_t e_1 = ecs_new(world, TagA); + ecs_add_id(world, e_1, TagB); + ecs_add_id(world, e_1, TagC); + ecs_add_id(world, e_1, TagD); + ecs_add_id(world, e_1, TagE); + ecs_add_id(world, e_1, TagF); + ecs_add_id(world, e_1, TagG); + ecs_add_id(world, e_1, TagH); + ecs_add_id(world, e_1, TagI); + ecs_add_id(world, e_1, TagJ); + ecs_add_id(world, e_1, TagK); + ecs_add_id(world, e_1, TagL); + ecs_add_id(world, e_1, TagM); + ecs_add_id(world, e_1, TagN); + ecs_add_id(world, e_1, TagO); + ecs_add_id(world, e_1, TagP); + ecs_add_id(world, e_1, TagQ); + ecs_add_id(world, e_1, TagR); + ecs_add_id(world, e_1, TagS); + ecs_add_id(world, e_1, TagT); + + ecs_entity_t e_2 = ecs_new(world, TagA); + ecs_add_id(world, e_2, TagB); + ecs_add_id(world, e_2, TagC); + ecs_add_id(world, e_2, TagD); + ecs_add_id(world, e_2, TagE); + ecs_add_id(world, e_2, TagF); + ecs_add_id(world, e_2, TagG); + ecs_add_id(world, e_2, TagH); + ecs_add_id(world, e_2, TagI); + ecs_add_id(world, e_2, TagJ); + ecs_add_id(world, e_2, TagK); + ecs_add_id(world, e_2, TagL); + ecs_add_id(world, e_2, TagM); + ecs_add_id(world, e_2, TagN); + ecs_add_id(world, e_2, TagO); + ecs_add_id(world, e_2, TagP); + ecs_add_id(world, e_2, TagQ); + ecs_add_id(world, e_2, TagR); + ecs_add_id(world, e_2, TagS); + ecs_add_id(world, e_2, TagT); + ecs_add_id(world, e_2, TagU); /* 2nd match in different table */ + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter = { + .terms_buffer = (ecs_term_t[]){ + {TagA}, {TagB}, {TagC}, {TagD}, {TagE}, {TagF}, {TagG}, {TagH}, + {TagI}, {TagJ}, {TagK}, {TagL}, {TagM}, {TagN}, {TagO}, {TagP}, + {TagQ}, {TagR}, {TagS}, {TagT} + }, + .terms_buffer_count = 20 + }, + }); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + test_int(ecs_term_id(&it, 11), TagK); + test_int(ecs_term_id(&it, 12), TagL); + test_int(ecs_term_id(&it, 13), TagM); + test_int(ecs_term_id(&it, 14), TagN); + test_int(ecs_term_id(&it, 15), TagO); + test_int(ecs_term_id(&it, 16), TagP); + test_int(ecs_term_id(&it, 17), TagQ); + test_int(ecs_term_id(&it, 18), TagR); + test_int(ecs_term_id(&it, 19), TagS); + test_int(ecs_term_id(&it, 20), TagT); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), TagA); + test_int(ecs_term_id(&it, 2), TagB); + test_int(ecs_term_id(&it, 3), TagC); + test_int(ecs_term_id(&it, 4), TagD); + test_int(ecs_term_id(&it, 5), TagE); + test_int(ecs_term_id(&it, 6), TagF); + test_int(ecs_term_id(&it, 7), TagG); + test_int(ecs_term_id(&it, 8), TagH); + test_int(ecs_term_id(&it, 9), TagI); + test_int(ecs_term_id(&it, 10), TagJ); + test_int(ecs_term_id(&it, 11), TagK); + test_int(ecs_term_id(&it, 12), TagL); + test_int(ecs_term_id(&it, 13), TagM); + test_int(ecs_term_id(&it, 14), TagN); + test_int(ecs_term_id(&it, 15), TagO); + test_int(ecs_term_id(&it, 16), TagP); + test_int(ecs_term_id(&it, 17), TagQ); + test_int(ecs_term_id(&it, 18), TagR); + test_int(ecs_term_id(&it, 19), TagS); + test_int(ecs_term_id(&it, 20), TagT); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +typedef struct { + float v; +} CompA, CompB, CompC, CompD, CompE, CompF, CompG, CompH, CompI, CompJ, CompK, + CompL, CompM, CompN, CompO, CompP, CompQ, CompR, CompS, CompT, CompU; + +void Query_query_iter_10_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, CompA); + ECS_COMPONENT(world, CompB); + ECS_COMPONENT(world, CompC); + ECS_COMPONENT(world, CompD); + ECS_COMPONENT(world, CompE); + ECS_COMPONENT(world, CompF); + ECS_COMPONENT(world, CompG); + ECS_COMPONENT(world, CompH); + ECS_COMPONENT(world, CompI); + ECS_COMPONENT(world, CompJ); + ECS_COMPONENT(world, CompK); + + ecs_entity_t e_1 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_1, CompB, {10}); + ecs_set(world, e_1, CompC, {10}); + ecs_set(world, e_1, CompD, {10}); + ecs_set(world, e_1, CompE, {10}); + ecs_set(world, e_1, CompF, {10}); + ecs_set(world, e_1, CompG, {10}); + ecs_set(world, e_1, CompH, {10}); + ecs_set(world, e_1, CompI, {10}); + ecs_set(world, e_1, CompJ, {10}); + + ecs_entity_t e_2 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_2, CompB, {10}); + ecs_set(world, e_2, CompC, {10}); + ecs_set(world, e_2, CompD, {10}); + ecs_set(world, e_2, CompE, {10}); + ecs_set(world, e_2, CompF, {10}); + ecs_set(world, e_2, CompG, {10}); + ecs_set(world, e_2, CompH, {10}); + ecs_set(world, e_2, CompI, {10}); + ecs_set(world, e_2, CompJ, {10}); + ecs_set(world, e_2, CompK, {10}); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter.terms = { + {ecs_id(CompA)}, {ecs_id(CompB)}, {ecs_id(CompC)}, {ecs_id(CompD)}, + {ecs_id(CompE)}, {ecs_id(CompF)}, {ecs_id(CompG)}, {ecs_id(CompH)}, + {ecs_id(CompI)}, {ecs_id(CompJ)} + } + }); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + + int i; + for (i = 0; i < 10; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + + for (i = 0; i < 10; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Query_query_iter_20_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, CompA); + ECS_COMPONENT(world, CompB); + ECS_COMPONENT(world, CompC); + ECS_COMPONENT(world, CompD); + ECS_COMPONENT(world, CompE); + ECS_COMPONENT(world, CompF); + ECS_COMPONENT(world, CompG); + ECS_COMPONENT(world, CompH); + ECS_COMPONENT(world, CompI); + ECS_COMPONENT(world, CompJ); + ECS_COMPONENT(world, CompK); + ECS_COMPONENT(world, CompL); + ECS_COMPONENT(world, CompM); + ECS_COMPONENT(world, CompN); + ECS_COMPONENT(world, CompO); + ECS_COMPONENT(world, CompP); + ECS_COMPONENT(world, CompQ); + ECS_COMPONENT(world, CompR); + ECS_COMPONENT(world, CompS); + ECS_COMPONENT(world, CompT); + ECS_COMPONENT(world, CompU); + + ecs_entity_t e_1 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_1, CompB, {10}); + ecs_set(world, e_1, CompC, {10}); + ecs_set(world, e_1, CompD, {10}); + ecs_set(world, e_1, CompE, {10}); + ecs_set(world, e_1, CompF, {10}); + ecs_set(world, e_1, CompG, {10}); + ecs_set(world, e_1, CompH, {10}); + ecs_set(world, e_1, CompI, {10}); + ecs_set(world, e_1, CompJ, {10}); + ecs_set(world, e_1, CompK, {10}); + ecs_set(world, e_1, CompL, {10}); + ecs_set(world, e_1, CompM, {10}); + ecs_set(world, e_1, CompN, {10}); + ecs_set(world, e_1, CompO, {10}); + ecs_set(world, e_1, CompP, {10}); + ecs_set(world, e_1, CompQ, {10}); + ecs_set(world, e_1, CompR, {10}); + ecs_set(world, e_1, CompS, {10}); + ecs_set(world, e_1, CompT, {10}); + + ecs_entity_t e_2 = ecs_set(world, 0, CompA, {10}); + ecs_set(world, e_2, CompB, {10}); + ecs_set(world, e_2, CompC, {10}); + ecs_set(world, e_2, CompD, {10}); + ecs_set(world, e_2, CompE, {10}); + ecs_set(world, e_2, CompF, {10}); + ecs_set(world, e_2, CompG, {10}); + ecs_set(world, e_2, CompH, {10}); + ecs_set(world, e_2, CompI, {10}); + ecs_set(world, e_2, CompJ, {10}); + ecs_set(world, e_2, CompK, {10}); + ecs_set(world, e_2, CompL, {10}); + ecs_set(world, e_2, CompM, {10}); + ecs_set(world, e_2, CompN, {10}); + ecs_set(world, e_2, CompO, {10}); + ecs_set(world, e_2, CompP, {10}); + ecs_set(world, e_2, CompQ, {10}); + ecs_set(world, e_2, CompR, {10}); + ecs_set(world, e_2, CompS, {10}); + ecs_set(world, e_2, CompT, {10}); + ecs_set(world, e_2, CompU, {10}); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t) { + .filter = { + .terms_buffer = (ecs_term_t[]){ + {ecs_id(CompA)}, {ecs_id(CompB)}, {ecs_id(CompC)}, {ecs_id(CompD)}, + {ecs_id(CompE)}, {ecs_id(CompF)}, {ecs_id(CompG)}, {ecs_id(CompH)}, + {ecs_id(CompI)}, {ecs_id(CompJ)}, {ecs_id(CompK)}, {ecs_id(CompL)}, + {ecs_id(CompM)}, {ecs_id(CompN)}, {ecs_id(CompO)}, {ecs_id(CompP)}, + {ecs_id(CompQ)}, {ecs_id(CompR)}, {ecs_id(CompS)}, {ecs_id(CompT)} + }, + .terms_buffer_count = 20 + } + }); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_1); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + test_int(ecs_term_id(&it, 11), ecs_id(CompK)); + test_int(ecs_term_id(&it, 12), ecs_id(CompL)); + test_int(ecs_term_id(&it, 13), ecs_id(CompM)); + test_int(ecs_term_id(&it, 14), ecs_id(CompN)); + test_int(ecs_term_id(&it, 15), ecs_id(CompO)); + test_int(ecs_term_id(&it, 16), ecs_id(CompP)); + test_int(ecs_term_id(&it, 17), ecs_id(CompQ)); + test_int(ecs_term_id(&it, 18), ecs_id(CompR)); + test_int(ecs_term_id(&it, 19), ecs_id(CompS)); + test_int(ecs_term_id(&it, 20), ecs_id(CompT)); + + int i; + for (i = 0; i < 20; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e_2); + test_int(ecs_term_id(&it, 1), ecs_id(CompA)); + test_int(ecs_term_id(&it, 2), ecs_id(CompB)); + test_int(ecs_term_id(&it, 3), ecs_id(CompC)); + test_int(ecs_term_id(&it, 4), ecs_id(CompD)); + test_int(ecs_term_id(&it, 5), ecs_id(CompE)); + test_int(ecs_term_id(&it, 6), ecs_id(CompF)); + test_int(ecs_term_id(&it, 7), ecs_id(CompG)); + test_int(ecs_term_id(&it, 8), ecs_id(CompH)); + test_int(ecs_term_id(&it, 9), ecs_id(CompI)); + test_int(ecs_term_id(&it, 10), ecs_id(CompJ)); + test_int(ecs_term_id(&it, 11), ecs_id(CompK)); + test_int(ecs_term_id(&it, 12), ecs_id(CompL)); + test_int(ecs_term_id(&it, 13), ecs_id(CompM)); + test_int(ecs_term_id(&it, 14), ecs_id(CompN)); + test_int(ecs_term_id(&it, 15), ecs_id(CompO)); + test_int(ecs_term_id(&it, 16), ecs_id(CompP)); + test_int(ecs_term_id(&it, 17), ecs_id(CompQ)); + test_int(ecs_term_id(&it, 18), ecs_id(CompR)); + test_int(ecs_term_id(&it, 19), ecs_id(CompS)); + test_int(ecs_term_id(&it, 20), ecs_id(CompT)); + + for (i = 0; i < 20; i ++) { + CompA *ptr = ecs_term_w_size(&it, 0, i + 1); + test_assert(ptr != NULL); + test_int(ptr[0].v, 10); + } + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Reference.c b/fggl/ecs2/flecs/test/api/src/Reference.c new file mode 100644 index 0000000000000000000000000000000000000000..f0afee150dab5653bee07be781d0d0c0d74d4ef8 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Reference.c @@ -0,0 +1,261 @@ +#include <api.h> + +void Reference_setup() { + ecs_tracing_enable(-3); +} + +void Reference_get_ref() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Reference_get_ref_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_add(world, e, Velocity); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Reference_get_ref_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + ecs_add(world, e, Velocity); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_remove(world, e, Velocity); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Reference_get_ref_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t dummy = ecs_new(world, Position); + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_delete(world, dummy); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Reference_get_ref_after_realloc() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_dim_type(world, ecs_type(Position), 1000); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Reference_get_ref_staged() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_defer_begin(world); + + ecs_set(world, e, Position, {30, 40}); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_defer_end(world); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Reference_get_ref_after_new_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_defer_begin(world); + + ecs_new(world, Position); + + ecs_set(world, e, Position, {30, 40}); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_defer_end(world); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Reference_get_ref_monitored() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + ecs_new_w_pair(world, EcsChildOf, e); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Reference_get_nonexisting() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_ref_t ref = {0}; + const Velocity *p = ecs_get_ref(world, &ref, e, Velocity); + test_assert(p == NULL); + + ecs_fini(world); +} + +static +void comp_move( + ecs_world_t *world, + ecs_entity_t component, + const ecs_entity_t *dst_entity, + const ecs_entity_t *src_entity, + void *dst_ptr, + void *src_ptr, + size_t size, + int32_t count, + void *ctx) +{ + memcpy(dst_ptr, src_ptr, size * count); +} + +void Reference_get_ref_after_realloc_w_lifecycle() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .move = comp_move + }); + + ECS_ENTITY(world, e, Position); + ECS_ENTITY(world, e2, Position); + ecs_set(world, e, Position, {10, 20}); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ECS_TYPE(world, Type, Position, Name); + ecs_dim_type(world, ecs_type(Type), 1000); + + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Remove.c b/fggl/ecs2/flecs/test/api/src/Remove.c new file mode 100644 index 0000000000000000000000000000000000000000..2c534220f4f99e7d00431d1e9acd1a63806f3f7a --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Remove.c @@ -0,0 +1,263 @@ +#include <api.h> + +void Remove_zero() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_remove(world, e, 0); + test_assert(!ecs_get_type(world, e)); + + ecs_fini(world); +} + +void Remove_zero_from_nonzero() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_remove(world, e, 0); + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Remove_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Remove_1_of_1_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Remove_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Remove_2_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_remove(world, e, Velocity); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Remove_2_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_remove(world, e, Velocity); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_fini(world); +} + +void Remove_2_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_remove(world, e, Position); + ecs_remove(world, e, Velocity); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_remove(world, e, Position); + ecs_remove(world, e, Velocity); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Remove_2_overlap() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_remove(world, e, Position); + ecs_remove(world, e, Velocity); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_fini(world); +} + +void Remove_type_of_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type_1, Position, Velocity); + ECS_TYPE(world, Type_2, Position); + + ecs_entity_t e = ecs_new(world, Type_1); + test_assert(e != 0); + + ecs_remove(world, e, Type_2); + test_assert(!ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Remove_type_of_2_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_remove(world, e, Type); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Remove_type_of_2_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type_1, Position, Velocity, Mass); + ECS_TYPE(world, Type_2, Position, Velocity); + + ecs_entity_t e = ecs_new(world, Type_1); + test_assert(e != 0); + + ecs_remove(world, e, Type_2); + test_assert(!ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + test_assert(ecs_has(world, e, Mass)); + + ecs_fini(world); +} + +void Remove_1_from_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Remove_type_from_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_remove(world, e, Type); + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Remove_not_added() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_remove(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Remove_bulk.c b/fggl/ecs2/flecs/test/api/src/Remove_bulk.c new file mode 100644 index 0000000000000000000000000000000000000000..388b5827e9df865be35eecef2f338a33c02960c2 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Remove_bulk.c @@ -0,0 +1,450 @@ +#include <api.h> + +void Remove_bulk_remove_comp_from_comp_to_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_remove(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_comp_from_comp_to_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_entity_t existing[10]; + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 10); + + ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, existing[i], Position)); + ecs_set(world, existing[i], Position, {i * 3, i * 4}); + } + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_remove(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 20); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_comp_from_tag_to_empty() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Tag, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( ecs_has(world, ids[i], Velocity)); + } + + ecs_bulk_remove(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( !ecs_has(world, ids[i], Velocity)); + } + + test_int( ecs_count(world, Tag), 10); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_comp_from_tag_to_existing() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Tag, Velocity); + + ecs_entity_t existing[10]; + const ecs_entity_t *ids = ecs_bulk_new(world, Tag, 10); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 10); + + ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != 0); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( ecs_has(world, ids[i], Velocity)); + } + + ecs_bulk_remove(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( !ecs_has(world, ids[i], Velocity)); + } + + test_int( ecs_count(world, Tag), 20); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_tag_from_tag_to_empty() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + ECS_TYPE(world, Type, Tag, Tag2); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( ecs_has(world, ids[i], Tag2)); + } + + ecs_bulk_remove(world, Tag2, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( !ecs_has(world, ids[i], Tag2)); + } + + test_int( ecs_count(world, Tag), 10); + test_int( ecs_count(world, Tag2), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_tag_from_tag_to_existing() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Tag2); + ECS_TYPE(world, Type, Tag, Tag2); + + ecs_entity_t existing[10]; + const ecs_entity_t *ids = ecs_bulk_new(world, Tag, 10); + test_assert(ids != NULL); + memcpy(existing, ids, sizeof(ecs_entity_t) * 10); + + ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( ecs_has(world, ids[i], Tag2)); + } + + ecs_bulk_remove(world, Tag2, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + test_assert( !ecs_has(world, ids[i], Tag2)); + } + + test_int( ecs_count(world, Tag), 20); + test_int( ecs_count(world, Tag2), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_all_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const ecs_entity_t *tmp_ids = ecs_bulk_new(world, Position, 10); + test_assert(tmp_ids != NULL); + + ecs_entity_t ids[10]; + memcpy(ids, tmp_ids, sizeof(ids)); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + } + + ecs_bulk_remove(world, Position, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( !ecs_has(world, ids[i], Position)); + } + + test_int( ecs_count(world, Position), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_all_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + const ecs_entity_t *ids = ecs_bulk_new(world, Tag, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Tag)); + } + + ecs_bulk_remove(world, Tag, &(ecs_filter_t){ + .include = ecs_type(Tag) + }); + + for (i = 0; i < 10; i ++) { + test_assert( !ecs_has(world, ids[i], Tag)); + } + + test_int( ecs_count(world, Tag), 0); + + ecs_fini(world); +} + +void RemoveVelocity(ecs_iter_t *it) { + probe_system(it); +} + +void Remove_bulk_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_TRIGGER(world, RemoveVelocity, EcsOnRemove, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + ecs_bulk_remove(world, Velocity, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 10); + test_int(ctx.system, RemoveVelocity); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + int i; + for (i = 0; i < 10; i ++) { + test_int(ctx.e[i], ids[i]); + } + + test_int(ctx.c[0][0], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Remove_bulk_remove_entity_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has(world, ids[i], Velocity)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_remove_entity(world, ecs_id(Velocity), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has(world, ids[i], Velocity)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count(world, Velocity), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_entity_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + ECS_TYPE(world, Type, Position, Tag); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( ecs_has_entity(world, ids[i], Tag)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + ecs_bulk_remove_entity(world, Tag, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + for (i = 0; i < 10; i ++) { + test_assert( ecs_has(world, ids[i], Position)); + test_assert( !ecs_has_entity(world, ids[i], Tag)); + + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, i); + test_int(p->y, i * 2); + } + + test_int( ecs_count(world, Position), 10); + test_int( ecs_count_entity(world, Tag), 0); + + ecs_fini(world); +} + +void Remove_bulk_remove_entity_on_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_TRIGGER(world, RemoveVelocity, EcsOnRemove, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 10); + test_assert(ids != NULL); + + ecs_bulk_remove_entity(world, ecs_id(Velocity), &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 10); + test_int(ctx.system, RemoveVelocity); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + int i; + for (i = 0; i < 10; i ++) { + test_int(ctx.e[i], ids[i]); + } + + test_int(ctx.c[0][0], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Remove_bulk_bulk_remove_w_low_tag_id() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t low_id = ecs_entity_init(world, &(ecs_entity_desc_t) { + .use_low_id = true + }); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_new_w_id(world, low_id); + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + ecs_bulk_remove(world, Position, &(ecs_filter_t){ + .include = ecs_type(Position) + }); + + test_assert(!ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e1, Velocity)); + test_assert(ecs_has_id(world, e1, low_id)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Run.c b/fggl/ecs2/flecs/test/api/src/Run.c new file mode 100644 index 0000000000000000000000000000000000000000..150e045b4b3a7c9ba2b95d81f8e3e347718e6e29 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Run.c @@ -0,0 +1,1320 @@ +#include <api.h> + +void Run_setup() { + ecs_tracing_enable(-3); +} + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + Velocity *v = NULL; + Mass *m = NULL; + + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + } + + if (it->column_count >= 3) { + m = ecs_term(it, Mass, 3); + } + + int *param = it->param; + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + + if (param) { + p[i].x += *param; + p[i].y += *param; + } + + if (v) { + v[i].x = 30; + v[i].y = 40; + } + + if (m) { + m[i] = 50; + } + } +} + +void Run_run() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run(world, Iter, 1.0, NULL), 0); + + test_int(ctx.count, 6); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.e[3], e4); + test_int(ctx.e[4], e5); + test_int(ctx.e[5], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_param() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + + ECS_SYSTEM(world, Iter, 0, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + int param = 5; + test_int( ecs_run(world, Iter, 1.0, ¶m), 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 1); + test_ptr(ctx.param, ¶m); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_int(p->x, 15); + test_int(p->y, 25); + + ecs_fini(world); +} + +void Run_run_w_offset() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 2, 0, 0, NULL), 0); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.e[1], e4); + test_int(ctx.e[2], e5); + test_int(ctx.e[3], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_skip_1_archetype() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 3, 0, 0, NULL), 0); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e4); + test_int(ctx.e[1], e5); + test_int(ctx.e[2], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_skip_1_archetype_plus_one() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 4, 0, 0, NULL), 0); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e5); + test_int(ctx.e[1], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_skip_2_archetypes() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 5, 0, 0, NULL), 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e6); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e6, Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, e6, Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_fini(world); +} + +void Run_run_w_limit_skip_1_archetype() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 5, 0, NULL), 0); + + test_int(ctx.count, 5); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.e[3], e4); + test_int(ctx.e[4], e5); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_limit_skip_1_archetype_minus_one() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 4, 0, NULL), 0); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.e[3], e4); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_limit_skip_2_archetypes() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 3, 0, NULL), 0); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_1_limit_max() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 1, 5, 0, NULL), 0); + + test_int(ctx.count, 5); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.e[1], e3); + test_int(ctx.e[2], e4); + test_int(ctx.e[3], e5); + test_int(ctx.e[4], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_1_limit_minus_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 1, 4, 0, NULL), 0); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.e[1], e3); + test_int(ctx.e[2], e4); + test_int(ctx.e[3], e5); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_2_type_limit_max() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 3, 3, 0, NULL), 0); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e4); + test_int(ctx.e[1], e5); + test_int(ctx.e[2], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_2_type_limit_minus_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 3, 2, 0, NULL), 0); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e4); + test_int(ctx.e[1], e5); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_limit_1_all_offsets() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + /* Process entities out of order so we can validate whether it is correct */ + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 1, 0, NULL), 0); + test_int( ecs_run_w_filter(world, Iter, 1.0, 2, 1, 0, NULL), 0); + test_int( ecs_run_w_filter(world, Iter, 1.0, 1, 1, 0, NULL), 0); + test_int( ecs_run_w_filter(world, Iter, 1.0, 3, 1, 0, NULL), 0); + test_int( ecs_run_w_filter(world, Iter, 1.0, 5, 1, 0, NULL), 0); + test_int( ecs_run_w_filter(world, Iter, 1.0, 4, 1, 0, NULL), 0); + + test_int(ctx.count, 6); + test_int(ctx.invoked, 6); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e3); + test_int(ctx.e[2], e2); + test_int(ctx.e[3], e4); + test_int(ctx.e[4], e6); + test_int(ctx.e[5], e5); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_offset_out_of_bounds() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 6, 1, 0, NULL), 0); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Run_run_w_limit_out_of_bounds() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 5, 2, 0, NULL), 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_component_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 0, &(ecs_filter_t){ + .include = ecs_type(Mass) + }, NULL), 0); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e4); + test_int(ctx.e[1], e5); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_type_filter_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type, Mass, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Mass, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 0, &(ecs_filter_t){ + .include = ecs_type(Type) + }, NULL), 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e6); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + test_int(ctx.c[i][1], ecs_id(Velocity)); + test_int(ctx.s[i][1], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + const Velocity *v = ecs_get(world, ctx.e[i], Velocity); + test_int(v->x, 30); + test_int(v->y, 40); + } + + ecs_fini(world); +} + +void Run_run_w_container_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_TYPE(world, Type, Mass, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Mass, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Iter, 0, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Create a parent entity */ + ecs_entity_t parent = ecs_new(world, 0); + + /* Adopt child entities */ + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e4, EcsChildOf, parent); + ecs_add_pair(world, e6, EcsChildOf, parent); + ecs_add_pair(world, e7, EcsChildOf, parent); + + /* Get type from parent to use as filter */ + ecs_type_t ecs_type(Parent) = ecs_type_from_id(world, ecs_pair(EcsChildOf, parent)); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run_w_filter(world, Iter, 1.0, 0, 0, &(ecs_filter_t){ + .include = ecs_type(Parent) + }, NULL), 0); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 4); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + probe_has_entity(&ctx, e1); + probe_has_entity(&ctx, e4); + probe_has_entity(&ctx, e6); + probe_has_entity(&ctx, e7); + + int i; + for (i = 0; i < ctx.invoked; i ++) { + test_int(ctx.c[i][0], ecs_id(Position)); + test_int(ctx.s[i][0], 0); + } + + for (i = 0; i < ctx.count; i ++) { + const Position *p = ecs_get(world, ctx.e[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + } + + ecs_fini(world); +} + +void Run_run_no_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position); + + ECS_SYSTEM(world, Iter, 0, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* Ensure system is not run by ecs_progress */ + ecs_progress(world, 1); + test_int(ctx.invoked, 0); + + test_int( ecs_run(world, Iter, 1.0, NULL), 0); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +typedef struct Param { + ecs_entity_t entity; + int count; +} Param; + +static +void TestSubset(ecs_iter_t *it) { + Param *param = it->param; + + int i; + for (i = 0; i < it->count; i ++) { + test_assert(param->entity != it->entities[i]); + param->count ++; + } +} + +static +void TestAll(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + ecs_entity_t TestSubset = ecs_term_id(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + Param param = {.entity = it->entities[i], 0}; + ecs_run_w_filter(it->world, TestSubset, 1, it->frame_offset + i + 1, 0, 0, ¶m); + p[i].x += param.count; + } +} + +void Run_run_comb_10_entities_1_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TestSubset, 0, Position); + ECS_SYSTEM(world, TestAll, EcsOnUpdate, Position, :TestSubset); + + int i, ENTITIES = 10; + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, ENTITIES); + + for (i = 0; i < ENTITIES; i ++) { + ecs_set(world, ids[i], Position, {1, 2}); + } + + ecs_progress(world, 0); + + for (i = 0; i < ENTITIES; i ++) { + const Position *p = ecs_get(world, ids[i], Position); + test_int(p->x, ENTITIES - i); + } + + ecs_fini(world); +} + +void Run_run_comb_10_entities_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, TestSubset, 0, Position); + ECS_SYSTEM(world, TestAll, EcsOnUpdate, Position, :TestSubset); + + int i, ENTITIES = 10; + + const ecs_entity_t *temp_ids_1 = ecs_bulk_new(world, Position, ENTITIES / 2); + ecs_entity_t ids_1[5]; + memcpy(ids_1, temp_ids_1, sizeof(ecs_entity_t) * ENTITIES / 2); + const ecs_entity_t *ids_2 = ecs_bulk_new(world, Type, ENTITIES / 2); + + for (i = 0; i < 5; i ++) { + ecs_set(world, ids_1[i], Position, {1, 2}); + ecs_set(world, ids_2[i], Position, {1, 2}); + } + + ecs_progress(world, 0); + + for (i = 0; i < 5; i ++) { + const Position *p = ecs_get(world, ids_1[i], Position); + test_int(p->x, ENTITIES - i); + + p = ecs_get(world, ids_2[i], Position); + test_int(p->x, ENTITIES - (i + 5)); + } + + ecs_fini(world); +} + +static +void Interrupt(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + if (i == 2) { + it->interrupted_by = it->entities[i]; + break; + } + } +} + +void Run_run_w_interrupt() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity, Mass); + ECS_ENTITY(world, e5, Position, Velocity, Mass); + ECS_ENTITY(world, e6, Position, Velocity, Mass, Rotation); + ECS_ENTITY(world, e7, Position); + + ECS_SYSTEM(world, Interrupt, 0, Position); + + ecs_entity_t e = ecs_run(world, Interrupt, 0, NULL); + test_int(e, e3); + + ecs_fini(world); +} + +static +void AddVelocity(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + + float x = p[i].x; + float y = p[i].y; + + ecs_set(world, e, Position, {x + 1, y + 2}); + const Position *p_stage = ecs_get(world, e, Position); + test_int(p_stage->x, x); + test_int(p_stage->y, y); + + /* Main stage isn't updated until after merge */ + test_int(p[i].x, x); + test_int(p[i].y, y); + + ecs_set(world, e, Velocity, {1, 2}); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v == NULL); + } +} + +void Run_run_staging() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {30, 40}); + + ECS_SYSTEM(world, AddVelocity, 0, Position, :Velocity); + + ecs_run(world, AddVelocity, 0, NULL); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + + const Position *p = ecs_get(world, e1, Position); + test_int(p->x, 11); + test_int(p->y, 22); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + + p = ecs_get(world, e2, Position); + test_int(p->x, 31); + test_int(p->y, 42); + + v = ecs_get(world, e2, Velocity); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Set.c b/fggl/ecs2/flecs/test/api/src/Set.c new file mode 100644 index 0000000000000000000000000000000000000000..534bd2de0f424e49ab9f3a7144de198be3174baf --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Set.c @@ -0,0 +1,649 @@ +#include <api.h> + +void Set_set_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_nonempty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_set(world, e, Velocity, {10, 20}); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + + ecs_fini(world); +} + +void Set_set_non_empty_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_set(world, e, Position, {30, 40}); + test_assert(ecs_has(world, e, Position)); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void Set_set_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_set(world, e, Velocity, {30, 40}); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_fini(world); +} + +void Set_add_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_add_other() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + p = ecs_get(world, e, Position); + test_assert(p == NULL); + + ecs_fini(world); +} + +void Set_set_remove_other() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Velocity); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_has(world, e, Velocity)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_remove(world, e, Velocity); + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_remove_twice() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + p = ecs_get(world, e, Position); + test_assert(p == NULL); + + ecs_set(world, e, Position, {10, 20}); + test_assert(ecs_has(world, e, Position)); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_and_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Set_set_null() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + ecs_set_ptr(world, e, Position, NULL); + test_assert(e != 0); + + test_assert(ecs_has(world, e, Position)); + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + + ecs_fini(world); +} + +void Set_get_mut_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + bool is_added = false; + Position *p = ecs_get_mut(world, e, Position, &is_added); + test_assert(p != NULL); + test_bool(is_added, true); + test_assert( ecs_has(world, e, Position)); + test_assert(p == ecs_get(world, e, Position)); + + ecs_fini(world); +} + +void Set_get_mut_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + const Position *p_prev = ecs_get(world, e, Position); + + bool is_added = false; + Position *p = ecs_get_mut(world, e, Position, &is_added); + test_assert(p != NULL); + test_bool(is_added, false); + test_assert( ecs_has(world, e, Position)); + test_assert(p == p_prev); + + ecs_fini(world); +} + +void Set_get_mut_tag_new() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, MyTag); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_expect_abort(); + + bool is_added = false; + ecs_get_mut_w_entity(world, e, MyTag, &is_added); +} + +void Set_get_mut_tag_existing() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, MyTag); + + ecs_entity_t e = ecs_new(world, MyTag); + test_assert(e != 0); + test_assert( ecs_has(world, e, MyTag)); + + test_expect_abort(); + + bool is_added = false; + ecs_get_mut_w_entity(world, e, MyTag, &is_added); +} + +void Set_get_mut_tag_new_w_comp() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, MyTag); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_expect_abort(); + + bool is_added = false; + ecs_get_mut_w_entity(world, e, MyTag, &is_added); +} + +void Set_get_mut_tag_existing_w_comp() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, MyTag); + + ecs_entity_t e = ecs_new(world, MyTag); + test_assert(e != 0); + test_assert( ecs_has(world, e, MyTag)); + ecs_add(world, e, Position); + + test_expect_abort(); + + bool is_added = false; + ecs_get_mut_w_entity(world, e, MyTag, &is_added); +} + +void Set_get_mut_tag_new_w_pair() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + ECS_TAG(world, MyTag); + + ecs_entity_t e = ecs_new_w_entity(world, ecs_pair(ecs_id(Position), Pair)); + test_assert(e != 0); + + test_expect_abort(); + + bool is_added = false; + ecs_get_mut_w_entity(world, e, MyTag, &is_added); +} + +void Set_get_mut_tag_existing_w_pair() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + ECS_TAG(world, MyTag); + + ecs_entity_t e = ecs_new(world, MyTag); + test_assert(e != 0); + test_assert( ecs_has(world, e, MyTag)); + ecs_add_pair(world, e, Pair, ecs_id(Position)); + + test_expect_abort(); + + bool is_added = false; + ecs_get_mut_w_entity(world, e, MyTag, &is_added); +} + +static bool is_invoked = false; + +void OnSetPosition(ecs_iter_t *it) { + is_invoked = true; +} + +void Set_modified_w_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, OnSetPosition, EcsOnSet, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + bool is_added = false; + Position *p = ecs_get_mut(world, e, Position, &is_added); + test_assert(p != NULL); + test_bool(is_added, true); + test_assert( ecs_has(world, e, Position)); + test_assert(p == ecs_get(world, e, Position)); + + test_assert(is_invoked == false); + ecs_modified(world, e, Position); + test_assert(is_invoked == true); + + ecs_fini(world); +} + +void Set_modified_no_component() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, OnSetPosition, EcsOnSet, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_expect_abort(); + + /* This should trigger an assert */ + ecs_modified(world, e, Position); + + ecs_fini(world); +} + +ECS_COMPONENT_EXTERN(Position); +ECS_COMPONENT_EXTERN(Velocity); + +static +void OnAdd(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +static +void OnAddRemove(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + ecs_remove(it->world, it->entities[i], Position); + } +} + +void Set_get_mut_w_add_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + ECS_COMPONENT_DEFINE(world, Velocity); + + ECS_TRIGGER(world, OnAdd, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new_id(world); + + bool added = false; + Position *p = ecs_get_mut(world, e, Position, &added); + test_assert(p != NULL); + test_bool(added, true); + p->x = 10; + p->y = 20; + + const Position *pc = ecs_get(world, e, Position); + test_int(pc->x, 10); + test_int(pc->y, 20); + + ecs_fini(world); +} + +void Set_get_mut_w_remove_in_on_add() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT_DEFINE(world, Position); + ECS_TRIGGER(world, OnAddRemove, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new_id(world); + + test_expect_abort(); + + /* get_mut is guaranteed to always return a valid pointer, so removing the + * component from the OnAdd trigger is not allowed */ + bool added = false; + ecs_get_mut(world, e, Position, &added); +} + +void Set_emplace() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + Position *p = ecs_emplace(world, e, Position); + test_assert(p != NULL); + test_assert(ecs_has(world, e, Position)); + test_assert(p == ecs_get(world, e, Position)); + + ecs_fini(world); +} + +void Set_emplace_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + Position *p = ecs_emplace(world, e, Position); + test_assert(p != NULL); + test_assert(ecs_has(world, e, Position)); + test_assert(p == ecs_get(world, e, Position)); + + Velocity *v = ecs_emplace(world, e, Velocity); + test_assert(v != NULL); + test_assert(ecs_has(world, e, Velocity)); + test_assert(v == ecs_get(world, e, Velocity)); + + ecs_fini(world); +} + +void Set_emplace_existing() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(ecs_has(world, e, Position)); + test_assert(e != 0); + + test_expect_abort(); + ecs_emplace(world, e, Position); +} + +void Set_emplace_w_move() { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + // Create entity with name, as name is component that is moved + ecs_entity_t e = ecs_set_name(world, 0, "Foo"); + test_assert(e != 0); + test_str("Foo", ecs_get_name(world, e)); + + Position *p = ecs_emplace(world, e, Position); + test_assert(p != NULL); + test_assert(ecs_has(world, e, Position)); + test_assert(p == ecs_get(world, e, Position)); + + test_str("Foo", ecs_get_name(world, e)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SingleThreadStaging.c b/fggl/ecs2/flecs/test/api/src/SingleThreadStaging.c new file mode 100644 index 0000000000000000000000000000000000000000..8a6de5c1b93276f182ec6c8bbc3c19bde3b72545 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SingleThreadStaging.c @@ -0,0 +1,2969 @@ +#include <api.h> +#include <flecs/type.h> + +void SingleThreadStaging_setup() { + ecs_tracing_enable(-3); +} + +static +void NewEmpty(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ctx->new_entities[ctx->entity_count] = ecs_new(it->world, 0); + ctx->entity_count ++; + } +} + +void SingleThreadStaging_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, NewEmpty, EcsOnUpdate, Position); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_get_type(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +static +void New_w_component(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e; + if (ctx->component) { + e = ecs_new_w_entity(it->world, ctx->component); + } else { + e = ecs_new_w_type(it->world, ctx->type); + } + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_new_w_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, New_w_component, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Velocity)); + } + + ecs_fini(world); +} + +void SingleThreadStaging_new_w_type_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, New_w_component, EcsOnUpdate, Position); + + IterData ctx = {.type = ecs_type(Type)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Position)); + test_assert( ecs_has(world, ctx.new_entities[i], Velocity)); + } + + ecs_fini(world); +} + +static +void NewEmpty_w_count(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ctx->new_entities[ctx->entity_count] = ecs_bulk_new(it->world, 0, 1000)[0]; + ctx->entity_count ++; +} + +void SingleThreadStaging_new_empty_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, e1, Position); + ECS_SYSTEM(world, NewEmpty_w_count, EcsOnUpdate, Position); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 1); + test_assert(ctx.new_entities[0] != 0); + + int i; + for (i = 0; i < 1000; i ++) { + test_assert( !ecs_get_type(world, ctx.new_entities[0] + i)); + } + + ecs_fini(world); +} + +static +void New_w_component_w_count(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + const ecs_entity_t *ids; + if (ctx->component) { + ids = ecs_bulk_new_w_entity(it->world, ctx->component, 1000); + } else { + ids = ecs_bulk_new_w_type(it->world, ctx->type, 1000); + } + ctx->new_entities[ctx->entity_count] = ids[0]; + ctx->entity_count ++; +} + +void SingleThreadStaging_new_component_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, e1, Position); + ECS_SYSTEM(world, New_w_component_w_count, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 1); + test_assert(ctx.new_entities[0] != 0); + test_int(ecs_count(world, Position), 1001); + + int i; + for (i = 0; i < 1000; i ++) { + test_assert( ecs_has(world, ctx.new_entities[0] + i, Position)); + } + + ecs_fini(world); +} + +void SingleThreadStaging_new_type_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_ENTITY(world, e1, Position); + ECS_SYSTEM(world, New_w_component_w_count, EcsOnUpdate, Position); + + IterData ctx = {.type = ecs_type(Type)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 1); + test_assert(ctx.new_entities[0] != 0); + test_int(ecs_count(world, Type), 1000); + + int i; + for (i = 0; i < 1000; i ++) { + test_assert( ecs_has(world, ctx.new_entities[0] + i, Type)); + } + + ecs_fini(world); +} + +static +void Add_to_new_empty(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + if (ctx->component) { + ecs_add_id(it->world, e, ctx->component); + } + if (ctx->component_2) { + ecs_add_id(it->world, e, ctx->component_2); + } + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_add_to_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_to_new_empty, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Velocity)); + } + + ecs_fini(world); +} + +void SingleThreadStaging_2_add_to_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_to_new_empty, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Velocity), .component_2 = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Velocity)); + test_assert( ecs_has(world, ctx.new_entities[i], Rotation)); + } + + ecs_fini(world); +} + +static +void Add_remove_same_from_new(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + + test_assert( !ecs_get_type(it->world, e)); + + if (ctx->component) { + ecs_add_id(it->world, e, ctx->component); + ecs_remove_id(it->world, e, ctx->component); + } + + if (ctx->component_2) { + ecs_add_id(it->world, e, ctx->component_2); + ecs_remove_id(it->world, e, ctx->component_2); + } + + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_add_remove_same_to_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_remove_same_from_new, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_get_type(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +void SingleThreadStaging_add_remove_2_same_to_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_remove_same_from_new, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_get_type(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +static +void Add_remove_same_from_new_w_component(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new_w_entity(it->world, ctx->component); + + if (ctx->component_2) { + ecs_add_id(it->world, e, ctx->component_2); + ecs_remove_id(it->world, e, ctx->component_2); + } + + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_add_remove_same_to_new_w_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_remove_same_from_new_w_component, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Position)); + test_assert( !ecs_has(world, ctx.new_entities[i], Velocity)); + } + + ecs_fini(world); +} + +static +void Add_remove_different_from_new_empty(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + + if (ctx->component_3) { + ecs_add_id(it->world, e, ctx->component_3); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, e, ctx->component_2); + } + + if (ctx->component) { + ecs_add_id(it->world, e, ctx->component); + } + + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_2_add_1_remove_to_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_remove_different_from_new_empty, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Position), .component_3 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Rotation)); + test_assert( ecs_has(world, ctx.new_entities[i], Mass)); + test_assert( !ecs_has(world, ctx.new_entities[i], Position)); + } + + ecs_fini(world); +} + +void SingleThreadStaging_2_add_1_remove_same_to_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_remove_different_from_new_empty, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Position), .component_3 = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Rotation)); + test_assert( !ecs_has(world, ctx.new_entities[i], Position)); + } + + ecs_fini(world); +} + +static +void Clone_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ctx->new_entities[ctx->entity_count] = ecs_clone(it->world, 0, it->entities[i], false); + ctx->entity_count ++; + } +} + +void SingleThreadStaging_clone() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Clone_current, EcsOnUpdate, Position); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert(ctx.new_entities[0] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[0])); + test_assert( ecs_has(world, ctx.new_entities[0], Position)); + + test_assert(ctx.new_entities[1] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[1])); + test_assert( ecs_has(world, ctx.new_entities[1], Position)); + test_assert( ecs_has(world, ctx.new_entities[1], Velocity)); + + test_assert(ctx.new_entities[2] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[2])); + test_assert( ecs_has(world, ctx.new_entities[2], Position)); + test_assert( ecs_has(world, ctx.new_entities[2], Mass)); + + ecs_fini(world); +} + +static +void Clone_current_w_value(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ctx->new_entities[ctx->entity_count] = ecs_clone(it->world, 0, it->entities[i], true); + ctx->entity_count ++; + } +} + +void SingleThreadStaging_clone_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Clone_current_w_value, EcsOnUpdate, Position); + + ecs_set(world, e1, Position, {10, 20}); + + ecs_set(world, e2, Position, {11, 21}); + ecs_set(world, e2, Velocity, {30, 40}); + + ecs_set(world, e3, Position, {12, 22}); + ecs_set(world, e3, Mass, {50}); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert(ctx.new_entities[0] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[0])); + test_assert( ecs_has(world, ctx.new_entities[0], Position)); + test_int( ecs_get(world, ctx.new_entities[0], Position)->x, 10); + test_int( ecs_get(world, ctx.new_entities[0], Position)->y, 20); + + test_assert(ctx.new_entities[1] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[1])); + test_assert( ecs_has(world, ctx.new_entities[1], Position)); + test_assert( ecs_has(world, ctx.new_entities[1], Velocity)); + test_int( ecs_get(world, ctx.new_entities[1], Position)->x, 11); + test_int( ecs_get(world, ctx.new_entities[1], Position)->y, 21); + test_int( ecs_get(world, ctx.new_entities[1], Velocity)->x, 30); + test_int( ecs_get(world, ctx.new_entities[1], Velocity)->y, 40); + + test_assert(ctx.new_entities[2] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[2])); + test_assert( ecs_has(world, ctx.new_entities[2], Position)); + test_assert( ecs_has(world, ctx.new_entities[2], Mass)); + test_int( ecs_get(world, ctx.new_entities[2], Position)->x, 12); + test_int( ecs_get(world, ctx.new_entities[2], Position)->y, 22); + test_int( *ecs_get(world, ctx.new_entities[2], Mass), 50); + + ecs_fini(world); +} + +static +void Add_to_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + } + if (ctx->component_2) { + ecs_add_id(it->world, it->entities[i], ctx->component_2); + } + ctx->entity_count ++; + } +} + +void SingleThreadStaging_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Rotation)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +void SingleThreadStaging_2_add_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Velocity), .component_2 = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e1, Rotation)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +static +void Remove_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = it->count - 1; i >= 0; i --) { + ecs_entity_t e = it->entities[i]; + + if (ctx->component) { + ecs_remove_id(it->world, e, ctx->component); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, e, ctx->component_2); + } + + ctx->entity_count ++; + } +} + +void SingleThreadStaging_remove_from_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_get_type(world, e1)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +void SingleThreadStaging_remove_2_from_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e1, Velocity)); + test_assert( !ecs_get_type(world, e1)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e2, Velocity)); + test_assert( !ecs_get_type(world, e2)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( !ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +static +void Add_remove_same_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + ecs_remove_id(it->world, it->entities[i], ctx->component); + } + + if (ctx->component_2) { + ecs_add_id(it->world, it->entities[i], ctx->component_2); + ecs_remove_id(it->world, it->entities[i], ctx->component_2); + } + + ctx->entity_count ++; + } +} + +void SingleThreadStaging_add_remove_same_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_remove_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e1, Rotation)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( !ecs_has(world, e2, Rotation)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( !ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +void SingleThreadStaging_add_remove_same_existing_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_remove_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_get_type(world, e1)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +static +void Remove_add_same_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_remove_id(it->world, it->entities[i], ctx->component); + ecs_add_id(it->world, it->entities[i], ctx->component); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, it->entities[i], ctx->component_2); + ecs_add_id(it->world, it->entities[i], ctx->component_2); + } + + ctx->entity_count ++; + } +} + +void SingleThreadStaging_remove_add_same_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_add_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Rotation)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +void SingleThreadStaging_remove_add_same_existing_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_add_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +void SingleThreadStaging_add_remove_2_same_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_remove_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e1, Rotation)); + test_assert( !ecs_has(world, e1, Mass)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( !ecs_has(world, e2, Rotation)); + test_assert( !ecs_has(world, e2, Mass)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( !ecs_has(world, e3, Mass)); + test_assert( !ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +void SingleThreadStaging_add_remove_2_same_existing_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_remove_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e1, Velocity)); + test_assert( !ecs_get_type(world, e1)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e2, Velocity)); + test_assert( !ecs_get_type(world, e2)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( !ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +void SingleThreadStaging_remove_add_2_same_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_add_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Rotation)); + test_assert( ecs_has(world, e1, Mass)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + test_assert( ecs_has(world, e2, Mass)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +static +void AddRemoveAdd(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + ecs_remove(it->world, it->entities[i], Velocity); + ecs_add(it->world, it->entities[i], Velocity); + } +} + +void SingleThreadStaging_add_remove_add_same_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddRemoveAdd, EcsOnUpdate, Position, :Velocity); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + ecs_fini(world); +} + +void SingleThreadStaging_remove_add_2_same_existing_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_add_same_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Velocity)); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +static +void Add_remove_different_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component_3) { + ecs_add_id(it->world, it->entities[i], ctx->component_3); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, it->entities[i], ctx->component_2); + } + + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + } + + ctx->entity_count ++; + } +} + +void SingleThreadStaging_add_remove_different_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_remove_different_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Rotation)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +void SingleThreadStaging_2_add_1_remove_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_remove_different_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Position), .component_3 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Mass)); + test_assert( ecs_has(world, e1, Rotation)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Mass)); + test_assert( ecs_has(world, e2, Rotation)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +static +void Add_1_remove_2_different_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, it->entities[i], ctx->component_2); + } + + if (ctx->component_3) { + ecs_remove_id(it->world, it->entities[i], ctx->component_3); + } + + ctx->entity_count ++; + } +} + +void SingleThreadStaging_1_add_2_remove_to_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Add_1_remove_2_different_from_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation), .component_2 = ecs_id(Position), .component_3 = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Rotation)); + + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + + test_assert( !ecs_has(world, e3, Position)); + test_assert( !ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + + ecs_fini(world); +} + +static +void Delete_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_delete(it->world, it->entities[i]); + ctx->entity_count ++; + } +} + +void SingleThreadStaging_delete_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Delete_current, EcsOnUpdate, Position); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +static +void Delete_even(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + if (!(it->entities[i] % 2)) { + ecs_delete(it->world, it->entities[i]); + } + ctx->entity_count ++; + } +} + +void SingleThreadStaging_delete_even() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity); + ECS_ENTITY(world, e5, Position, Mass); + ECS_ENTITY(world, e6, Position, Mass); + ECS_ENTITY(world, e7, Position, Rotation); + ECS_ENTITY(world, e8, Position, Rotation); + ECS_SYSTEM(world, Delete_even, EcsOnUpdate, Position); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 8); + + if (!(e1 % 2)) + test_assert( !ecs_is_alive(world, e1)); + else + test_assert ( ecs_is_alive(world, e1)); + + if (!(e2 % 2)) + test_assert( !ecs_is_alive(world, e2)); + else + test_assert ( ecs_is_alive(world, e2)); + + if (!(e3 % 2)) + test_assert( !ecs_is_alive(world, e3)); + else + test_assert ( ecs_is_alive(world, e3)); + + if (!(e4 % 2)) + test_assert( !ecs_is_alive(world, e4)); + else + test_assert ( ecs_is_alive(world, e4)); + + if (!(e5 % 2)) + test_assert( !ecs_is_alive(world, e5)); + else + test_assert ( ecs_is_alive(world, e5)); + + if (!(e6 % 2)) + test_assert( !ecs_is_alive(world, e6)); + else + test_assert ( ecs_is_alive(world, e6)); + + if (!(e7 % 2)) + test_assert( !ecs_is_alive(world, e7)); + else + test_assert ( ecs_is_alive(world, e7)); + + if (!(e8 % 2)) + test_assert( !ecs_is_alive(world, e8)); + else + test_assert ( ecs_is_alive(world, e8)); + + ecs_fini(world); +} + +static +void Delete_new_empty(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + ecs_delete(it->world, e); + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_delete_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Delete_new_empty, EcsOnUpdate, Position); + + IterData ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_is_alive(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +static +void Delete_new_w_component(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new_w_entity(it->world, ctx->component); + ecs_delete(it->world, e); + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_delete_new_w_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + ECS_SYSTEM(world, Delete_new_w_component, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_is_alive(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +static +void Set_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + ecs_entity_t ecs_id(Rotation) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Rotation, {10 + it->entities[i]}); + ctx->entity_count ++; + } +} + +void SingleThreadStaging_set_current() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Set_current, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e1, Rotation)); + test_int( *ecs_get(world, e1, Rotation), e1 + 10); + + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e2, Rotation)); + test_int( *ecs_get(world, e2, Rotation), e2 + 10); + + test_assert( ecs_has(world, e3, Position)); + test_assert( ecs_has(world, e3, Velocity)); + test_assert( ecs_has(world, e3, Mass)); + test_assert( ecs_has(world, e3, Rotation)); + test_int( *ecs_get(world, e3, Rotation), e3 + 10); + + ecs_fini(world); +} + +static +void Set_new_empty(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + ecs_entity_t ecs_id(Rotation) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + ecs_set(it->world, e, Rotation, {10 + e}); + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_set_new_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Set_new_empty, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Rotation)); + test_int( *ecs_get(world, ctx.new_entities[i], Rotation), ctx.new_entities[i] + 10); + } + + ecs_fini(world); +} + +static +void Set_new_w_component(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Rotation) = ctx->component_2; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new_w_entity(it->world, ctx->component); + ecs_set(it->world, e, Rotation, {10 + e}); + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_set_new_w_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Set_new_w_component, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Position)); + test_assert( ecs_has(world, ctx.new_entities[i], Rotation)); + test_int( *ecs_get(world, ctx.new_entities[i], Rotation), ctx.new_entities[i] + 10); + } + + ecs_fini(world); +} + +static +void Set_existing_new_w_component(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Position) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new_w_entity(it->world, ctx->component); + ecs_set(it->world, e, Position, {10 + e, 20 + e}); + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_set_existing_new_w_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Set_existing_new_w_component, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Position)); + test_int( ecs_get(world, ctx.new_entities[i], Position)->x, ctx.new_entities[i] + 10); + test_int( ecs_get(world, ctx.new_entities[i], Position)->y, ctx.new_entities[i] + 20); + } + + ecs_fini(world); +} + +static +void Set_new_after_add(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Position) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + ecs_add_id(it->world, e, ctx->component); + ecs_set(it->world, e, Position, {10 + e, 20 + e}); + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_set_new_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Set_new_after_add, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !!ecs_get_type(world, ctx.new_entities[i])); + test_assert( ecs_has(world, ctx.new_entities[i], Position)); + test_int( ecs_get(world, ctx.new_entities[i], Position)->x, ctx.new_entities[i] + 10); + test_int( ecs_get(world, ctx.new_entities[i], Position)->y, ctx.new_entities[i] + 20); + } + + ecs_fini(world); +} + +static +void Remove_after_set(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Position) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + ecs_set(it->world, e, Position, {10 + e, 20 + e}); + ecs_remove_id(it->world, e, ctx->component); + + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_remove_after_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Remove_after_set, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_has(world, ctx.new_entities[i], Position)); + test_assert( !ecs_get_type(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +static +void Delete_after_set(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Position) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = ecs_new(it->world, 0); + test_assert( !ecs_get_type(it->world, e)); + + ecs_set(it->world, e, Position, {10 + e, 20 + e}); + ecs_delete(it->world, e); + + ctx->new_entities[ctx->entity_count] = e; + ctx->entity_count ++; + } +} + +void SingleThreadStaging_delete_after_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + ECS_SYSTEM(world, Delete_after_set, EcsOnUpdate, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + ecs_entity_t e3 = ecs_new(world, 0); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + + ecs_progress(world, 1); + + test_int(ctx.entity_count, 3); + + int i; + for (i = 0; i < ctx.entity_count; i ++) { + test_assert(ctx.new_entities[i] != 0); + test_assert( !ecs_is_alive(world, ctx.new_entities[i])); + } + + ecs_fini(world); +} + +void SingleThreadStaging_add_to_current_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TRIGGER(world, Add_to_current, EcsOnAdd, Position); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + ecs_fini(world); +} + +void SingleThreadStaging_remove_from_current_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Remove_from_current, EcsOnAdd, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, Type); + ecs_entity_t e2 = ecs_new(world, Type); + ecs_entity_t e3 = ecs_new(world, Type); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + ecs_fini(world); +} + +void SingleThreadStaging_remove_added_component_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Remove_from_current, EcsOnAdd, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, Type); + ecs_entity_t e2 = ecs_new(world, Type); + ecs_entity_t e3 = ecs_new(world, Type); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + ecs_fini(world); +} + +static Probe pv_probe; + +static +void On_PV(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + probe_system_w_ctx(it, &pv_probe); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} + +void SingleThreadStaging_match_table_created_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position, !Velocity); + ECS_SYSTEM(world, On_PV, EcsOnUpdate, Position, Velocity); + + IterData add_ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &add_ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_progress(world, 1); + + test_int(pv_probe.count, 3); + test_int(pv_probe.invoked, 1); + test_int(pv_probe.system, On_PV); + test_int(pv_probe.column_count, 2); + test_null(pv_probe.param); + + test_int(pv_probe.e[0], e1); + test_int(pv_probe.e[1], e2); + test_int(pv_probe.e[2], e3); + test_int(pv_probe.c[0][0], ecs_id(Position)); + test_int(pv_probe.s[0][0], 0); + test_int(pv_probe.c[0][1], ecs_id(Velocity)); + test_int(pv_probe.s[0][1], 0); + + ecs_fini(world); +} + +static +void Set_velocity_on_new(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Velocity) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, 0, Velocity, {10, 20}); + } +} + +static +void On_V(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + v[i].x = v[i].y; + } +} + +void SingleThreadStaging_match_table_created_w_new_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Set_velocity_on_new, EcsOnUpdate, Position); + ECS_SYSTEM(world, On_V, EcsOnUpdate, Velocity); + + IterData add_ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &add_ctx); + + ecs_set(world, 0, Position, {10, 20}); + + ecs_progress(world, 1); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + ecs_enable(world, Set_velocity_on_new, false); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, On_V); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.c[0][0], ecs_id(Velocity)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + + +void SingleThreadStaging_match_table_created_w_new_in_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Set_velocity_on_new, EcsOnSet, Position); + ECS_SYSTEM(world, On_V, EcsOnUpdate, Velocity); + + IterData add_ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &add_ctx); + + ecs_set(world, 0, Position, {10, 20}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + ecs_enable(world, Set_velocity_on_new, false); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, On_V); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.c[0][0], ecs_id(Velocity)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +static +void Create_container(ecs_iter_t *it) { + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_new_w_pair(it->world, EcsChildOf, it->entities[i]); + } +} + +void SingleThreadStaging_merge_table_w_container_added_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Create_container, EcsOnUpdate, Position); + + ECS_ENTITY(world, e1, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Create_container); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SingleThreadStaging_merge_table_w_container_added_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Create_container, EcsOnSet, Position); + + /* Entity is not yet a container. Adding this entity to another entity would + * cause an error */ + ECS_ENTITY(world, e1, 0); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_set(world, e1, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Create_container); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +/* Hacky way to test if entities are properly merged by system */ +static ecs_entity_t g_child = 0; +static ecs_entity_t g_parent = 0; + +static +void Create_container_reverse(ecs_iter_t *it) { + + probe_system(it); + + ecs_world_t *world = it->world; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t child = ecs_new(world, 0); + ecs_entity_t parent = ecs_new(world, 0); + test_assert(child != 0); + test_assert(parent != 0); + + ecs_add_pair(world, child, EcsChildOf, parent); + + g_parent = parent; + g_child = child; + } +} + +void SingleThreadStaging_merge_table_w_container_added_on_set_reverse() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Create_container_reverse, EcsOnSet, Position); + + /* Entity is not yet a container. Adding this entity to another entity would + * cause an error */ + ECS_ENTITY(world, e1, 0); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_set(world, e1, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Create_container_reverse); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + test_assert(g_parent != 0); + test_assert(g_child != 0); + + test_assert( ecs_has_pair(world, g_child, EcsChildOf, g_parent)); + + ecs_fini(world); +} + +static +void Task(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + ecs_entity_t *e = ecs_get_context(it->world); + + ecs_add(it->world, *e, Position); +} + +void SingleThreadStaging_merge_after_tasks() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, 0); + + ECS_SYSTEM(world, Task, EcsOnUpdate, :Position); + + ecs_set_context(world, &e); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Position)); + + ecs_fini(world); +} + +static +void OverrideAfterRemove(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 30); + test_int(p[i].y, 40); + ecs_remove(it->world, it->entities[i], Position); + ecs_add(it->world, it->entities[i], Position); + } +} + +void SingleThreadStaging_override_after_remove_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, base, Position, Disabled); + + ECS_ENTITY(world, e, Position, (IsA, base)); + + ECS_SYSTEM(world, OverrideAfterRemove, EcsOnUpdate, Position); + + ecs_set(world, base, Position, {10, 20}); + + ecs_set(world, e, Position, {30, 40}); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Position)); + const Position *p_ptr = ecs_get(world, e, Position); + test_assert(p_ptr != NULL); + test_int(p_ptr->x, 10); + test_int(p_ptr->y, 20); + + ecs_fini(world); +} + +static +void GetParentInProgress(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 2); + + ecs_world_t *world = it->world; + + /* Create parent */ + ecs_entity_t parent = ecs_new(world, Velocity); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_add_pair(world, e, EcsChildOf, parent); + } +} + +void SingleThreadStaging_get_parent_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, GetParentInProgress, EcsOnUpdate, Position, :Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 1); + + ecs_entity_t parent = ecs_get_parent(world, e, Velocity); + test_assert(parent != 0); + + ecs_fini(world); +} + +static +void AddInProgress(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(world, it->entities[i], Position, {1, 1}); + ecs_set(world, it->entities[i], Velocity, {2, 2}); + } +} + +static +void Move(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} + +void SingleThreadStaging_merge_once() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddInProgress, EcsOnUpdate, Position, !Velocity); + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {0, 0}); + ecs_progress(world, 1); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 3); + test_int(p->y, 3); + + const Velocity *v = ecs_get(world, e, Velocity); + test_int(v->x, 2); + test_int(v->y, 2); + + ecs_progress(world, 1); + ecs_progress(world, 1); + + p = ecs_get(world, e, Position); + test_int(p->x, 7); + test_int(p->y, 7); + + v = ecs_get(world, e, Velocity); + test_int(v->x, 2); + test_int(v->y, 2); + + ecs_fini(world); +} + +static int move_position = 0; +static +ECS_MOVE(Position, dst, src, { + move_position ++; + dst->x = src->x; + dst->y = src->y; +}); + +void SingleThreadStaging_clear_stage_after_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_set(world, ecs_id(Position), EcsComponentLifecycle, { + .move = ecs_move(Position) + }); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_defer_begin(world); + ecs_set(world, e, Position, {10, 20}); + ecs_defer_end(world); + + test_int(move_position, 1); + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_defer_begin(world); + ecs_set(world, e, Position, {30, 40}); + ecs_defer_end(world); + + test_int(move_position, 2); + p = ecs_get(world, e, Position); + test_int(p->x, 30); + test_int(p->y, 40); + + ecs_fini(world); +} + +void MutableTest(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ECS_COLUMN(it, Velocity, v, 2); + + int32_t i; + for (i = 0; i < it->count; i ++) { + bool is_added; + Velocity *v_mut = ecs_get_mut( + world, it->entities[i], Velocity, &is_added); + + test_assert(v_mut != NULL); + test_assert(v_mut != v); + + // Even though component is added to stage, is_added should only be true + // if the component is added for the first time, which requires the app + // to init the component value. + if (!v) { + test_bool(is_added, true); + } else { + test_bool(is_added, false); + } + + if (is_added) { + v_mut->x = 0; + v_mut->y = 0; + } + + v_mut->x ++; + v_mut->y ++; + + // Make sure we didn't update the main stage + if (v) { + test_assert(v->x == v_mut->x - 1); + test_assert(v->y == v_mut->y - 1); + } + } +} + +void SingleThreadStaging_get_mutable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, MutableTest, EcsOnUpdate, Position, ?Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 1); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 2); + test_int(v->y, 2); + + ecs_fini(world); +} + +void SingleThreadStaging_get_mutable_from_main() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, MutableTest, EcsOnUpdate, Position, ?Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + // Add velocity on main stage + ecs_set(world, e, Velocity, {1, 1}); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 2); + test_int(v->y, 2); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 3); + test_int(v->y, 3); + + ecs_fini(world); +} + +void MutableTest_w_Add(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ECS_COLUMN(it, Velocity, v, 2); + ECS_COLUMN_COMPONENT(it, MyTag, 3); + + int32_t i; + for (i = 0; i < it->count; i ++) { + ecs_add(world, it->entities[i], MyTag); + + bool is_added; + Velocity *v_mut = ecs_get_mut( + world, it->entities[i], Velocity, &is_added); + + // Even though component is added to stage, is_added should only be true + // if the component is added for the first time, which requires the app + // to init the component value. + if (!v) { + test_bool(is_added, true); + } else { + test_bool(is_added, false); + } + + if (is_added) { + v_mut->x = 0; + v_mut->y = 0; + } + + v_mut->x ++; + v_mut->y ++; + + // Make sure we didn't update the main stage + if (v) { + test_assert(v->x == v_mut->x - 1); + test_assert(v->y == v_mut->y - 1); + } + } +} + +typedef bool MyBool; + +void SingleThreadStaging_get_mutable_w_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, MyBool); + ECS_SYSTEM(world, MutableTest_w_Add, EcsOnUpdate, Position, ?Velocity, :MyBool); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 1); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 2); + test_int(v->y, 2); + + ecs_progress(world, 0); + test_assert( ecs_has(world, e, Velocity)); + + v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 3); + test_int(v->y, 3); + + ecs_fini(world); +} + +static +void OnAdd(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + int i; + for (i = 0; i < it->count; i ++) { + v->x = 1; + v->y = 2; + } +} + +static +void AddInProgress2(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +void SingleThreadStaging_on_add_after_new_type_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, AddInProgress2, EcsOnUpdate, Position, :Velocity); + ECS_TRIGGER(world, OnAdd, EcsOnAdd, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Velocity)); + const Velocity *v = ecs_get(world, e, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + ecs_fini(world); +} + +void SingleThreadStaging_new_type_from_entity() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_frame_begin(world, 1); + + ecs_staging_begin(world); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + test_expect_abort(); + + /* Type is guaranteed not to exist, should trigger assert because table + * creation is not allowed while in progress */ + ecs_type_from_id(world, e); +} + +void SingleThreadStaging_existing_type_from_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + /* Precreate type so that it can be looked up while in progress */ + ecs_type_t t = ecs_type_from_id(world, e); + test_assert(t != NULL); + + ecs_frame_begin(world, 1); + + ecs_staging_begin(world); + + ecs_type_from_id(world, e); + + ecs_staging_end(world); + + ecs_frame_end(world); + + ecs_fini(world); +} + +void SingleThreadStaging_new_type_add() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_frame_begin(world, 1); + + ecs_staging_begin(world); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + test_expect_abort(); + + /* Type is guaranteed not to exist, should trigger assert because table + * creation is not allowed while in progress */ + ecs_type_add(world, ecs_type(Position), e); +} + +void SingleThreadStaging_existing_type_add() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + + ECS_COMPONENT(world, Position); + + /* Precreate type so that it can be looked up while in progress */ + ecs_type_t t = ecs_type_add(world, ecs_type(Position), e); + test_assert(t != NULL); + + ecs_frame_begin(world, 1); + + ecs_staging_begin(world); + + ecs_type_add(world, ecs_type(Position), e); + + ecs_staging_end(world); + + ecs_frame_end(world); + + ecs_fini(world); +} + +void SingleThreadStaging_lock_table() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_id(Position)}} + }); + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + ecs_table_t *table = it.table; + test_assert(table != NULL); + + ecs_table_lock(world, table); + + test_expect_abort(); + ecs_remove(world, e, Position); + } + + test_assert(false); // Should never get here +} + +void SingleThreadStaging_recursive_lock_table() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_id(Position)}} + }); + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + ecs_table_t *table = it.table; + test_assert(table != NULL); + + ecs_table_lock(world, table); + ecs_table_lock(world, table); + ecs_table_unlock(world, table); + + test_expect_abort(); + ecs_remove(world, e, Position); + } + + test_assert(false); // Should never get here +} + +void SingleThreadStaging_modify_after_lock() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{ecs_id(Position)}} + }); + + int32_t count = 0; + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + ecs_table_t *table = it.table; + test_assert(table != NULL); + + ecs_table_lock(world, table); + count ++; + ecs_table_unlock(world, table); + } + + test_int(count, 1); + + ecs_remove(world, e, Position); + test_assert(!ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void SingleThreadStaging_get_empty_case_from_stage() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, CaseOne); + ECS_TAG(world, CaseTwo); + ECS_TYPE(world, Switch, CaseOne, CaseTwo); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, ECS_SWITCH | Switch); + + ecs_frame_begin(world, 1); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_entity_t c = ecs_get_case(world, e, Switch); + test_assert(c == 0); + + c = ecs_get_case(stage, e, Switch); + test_assert(c == 0); + + ecs_staging_end(world); + + ecs_frame_end(world); + + ecs_fini(world); +} + +void SingleThreadStaging_get_case_from_stage() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, CaseOne); + ECS_TAG(world, CaseTwo); + ECS_TYPE(world, Switch, CaseOne, CaseTwo); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, ECS_SWITCH | Switch); + ecs_add_id(world, e, ECS_CASE | CaseOne); + + ecs_frame_begin(world, 1); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_entity_t c = ecs_get_case(world, e, Switch); + test_assert(c == CaseOne); + + c = ecs_get_case(stage, e, Switch); + test_assert(c == CaseOne); + + ecs_staging_end(world); + + ecs_frame_end(world); + + ecs_fini(world); +} + +void SingleThreadStaging_get_object_from_stage() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, CaseOne); + ECS_TAG(world, CaseTwo); + ECS_TYPE(world, Switch, CaseOne, CaseTwo); + + ecs_entity_t parent = ecs_new_id(world); + ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, parent); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + + test_assert(parent == ecs_get_object(stage, e, EcsChildOf, 0)); + + ecs_staging_end(world); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Singleton.c b/fggl/ecs2/flecs/test/api/src/Singleton.c new file mode 100644 index 0000000000000000000000000000000000000000..0708990acc0f9ba5d90abd7e2ed0900e25033a55 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Singleton.c @@ -0,0 +1,60 @@ +#include <api.h> + +void Singleton_set_get_singleton() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_singleton_set(world, Position, {10, 20}); + + const Position *p = ecs_singleton_get(world, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Singleton_get_mut_singleton() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Position *p_mut = ecs_singleton_get_mut(world, Position); + p_mut->x = 10; + p_mut->y = 20; + + const Position *p = ecs_singleton_get(world, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +static +void IncSingleton(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + p->x ++; + p->y ++; +} + +void Singleton_singleton_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, IncSingleton, EcsOnUpdate, $Position); + + ecs_singleton_set(world, Position, {10, 20}); + + ecs_progress(world, 0); + + const Position *p = ecs_singleton_get(world, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 21); + + ecs_fini(world); +} + diff --git a/fggl/ecs2/flecs/test/api/src/Snapshot.c b/fggl/ecs2/flecs/test/api/src/Snapshot.c new file mode 100644 index 0000000000000000000000000000000000000000..ec21a1c4d10d78a80aab454bb58205cdc0ec2f26 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Snapshot.c @@ -0,0 +1,860 @@ +#include <api.h> + +void Snapshot_simple_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + Position *p = ecs_get_mut(world, e, Position, NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p->x ++; + p->y ++; + + ecs_snapshot_restore(world, s); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get_mut(world, e, Position, NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Snapshot_snapshot_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, e2)); + + test_assert(ecs_has(world, e, Position)); + test_assert(ecs_new(world, 0) == e2); + + ecs_fini(world); +} + +void Snapshot_snapshot_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_delete(world, e); + test_assert(!ecs_is_alive(world, e)); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_is_alive(world, e)); + test_assert(ecs_has(world, e, Position)); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Snapshot_snapshot_after_new_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_is_alive(world, e)); + test_assert(!ecs_is_alive(world, e2)); + + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Snapshot_snapshot_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Velocity)); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_has(world, e, Position)); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Snapshot_snapshot_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Velocity)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_remove(world, e, Velocity); + test_assert(!ecs_has(world, e, Velocity)); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_has(world, e, Position)); + + ecs_fini(world); +} + +void Snapshot_snapshot_w_include_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + + ecs_entity_t e2 = ecs_set(world, 0, Position, {15, 25}); + test_assert(e2 != 0); + ecs_add(world, e2, Velocity); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_has(world, e2, Velocity)); + + ecs_entity_t e3 = ecs_set(world, 0, Velocity, {25, 35}); + test_assert(e3 != 0); + test_assert(ecs_has(world, e3, Velocity)); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + + Position *p = ecs_get_mut(world, e1, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p->x ++; + p->y ++; + + p = ecs_get_mut(world, e2, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 15); + test_int(p->y, 25); + + p->x ++; + p->y ++; + + Velocity *v = ecs_get_mut(world, e3, Velocity, NULL); + test_assert(v != NULL); + test_int(v->x, 25); + test_int(v->y, 35); + + v->x ++; + v->y ++; + + ecs_snapshot_restore(world, s); + + /* Restored */ + p = ecs_get_mut(world, e1, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + /* Restored */ + p = ecs_get_mut(world, e2, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 15); + test_int(p->y, 25); + + /* Not restored */ + v = ecs_get_mut(world, e3, Velocity, NULL); + test_assert(v != NULL); + test_int(v->x, 26); + test_int(v->y, 36); + + test_assert(ecs_new(world, 0) > e3); + + ecs_fini(world); +} + +void Snapshot_snapshot_w_exclude_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + + ecs_entity_t e2 = ecs_set(world, 0, Position, {15, 25}); + test_assert(e2 != 0); + ecs_add(world, e2, Velocity); + test_assert(ecs_has(world, e2, Position)); + test_assert(ecs_has(world, e2, Velocity)); + + ecs_entity_t e3 = ecs_set(world, 0, Velocity, {25, 35}); + test_assert(e3 != 0); + test_assert(ecs_has(world, e3, Velocity)); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position), .oper = EcsNot }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + + Position *p = ecs_get_mut(world, e1, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p->x ++; + p->y ++; + + p = ecs_get_mut(world, e2, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 15); + test_int(p->y, 25); + + p->x ++; + p->y ++; + + Velocity *v = ecs_get_mut(world, e3, Velocity, NULL); + test_assert(v != NULL); + test_int(v->x, 25); + test_int(v->y, 35); + + v->x ++; + v->y ++; + + ecs_snapshot_restore(world, s); + + /* Not restored */ + p = ecs_get_mut(world, e1, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 21); + + /* Not restored */ + p = ecs_get_mut(world, e2, Position, NULL); + test_assert(p != NULL); + test_int(p->x, 16); + test_int(p->y, 26); + + /* Restored */ + v = ecs_get_mut(world, e3, Velocity, NULL); + test_assert(v != NULL); + test_int(v->x, 25); + test_int(v->y, 35); + + test_assert(ecs_new(world, 0) > e3); + + ecs_fini(world); +} + +void Snapshot_snapshot_w_filter_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {1, 2}); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + + ecs_entity_t e2 = ecs_set(world, 0, Velocity, {3, 4}); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Velocity)); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + + ecs_set(world, e1, Position, {5, 6}); + ecs_set(world, e2, Velocity, {7, 8}); + + ecs_entity_t e3 = ecs_set(world, 0, Position, {33, 44}); + test_assert(e3 != 0); + test_assert(ecs_has(world, e3, Position)); + + ecs_entity_t e4 = ecs_set(world, 0, Velocity, {34, 45}); + test_assert(e4 != 0); + test_assert(ecs_has(world, e4, Velocity)); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_has(world, e1, Position)); + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + test_assert(ecs_has(world, e2, Velocity)); + const Velocity *v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 7); + test_int(v->y, 8); + + test_assert(ecs_has(world, e3, Position)); + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 33); + test_int(p->y, 44); + + test_assert(ecs_has(world, e4, Velocity)); + v = ecs_get(world, e4, Velocity); + test_assert(p != NULL); + test_int(v->x, 34); + test_int(v->y, 45); + + ecs_fini(world); +} + +void Snapshot_snapshot_w_filter_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {1, 2}); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + + ecs_entity_t e2 = ecs_set(world, 0, Velocity, {3, 4}); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Velocity)); + + ecs_entity_t e3 = ecs_set(world, 0, Position, {1, 2}); + test_assert(e3 != 0); + test_assert(ecs_has(world, e3, Position)); + + ecs_entity_t e4 = ecs_set(world, 0, Velocity, {3, 4}); + test_assert(e4 != 0); + test_assert(ecs_has(world, e4, Velocity)); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + + ecs_delete(world, e3); + ecs_delete(world, e4); + + test_assert(!ecs_is_alive(world, e3)); + test_assert(!ecs_is_alive(world, e4)); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_is_alive(world, e1)); + test_assert(ecs_is_alive(world, e2)); + test_assert(ecs_is_alive(world, e3)); + test_assert(!ecs_is_alive(world, e4)); + + test_assert(ecs_has(world, e1, Position)); + test_assert(ecs_has(world, e2, Velocity)); + test_assert(ecs_has(world, e3, Position)); + + ecs_fini(world); +} + +void Snapshot_snapshot_free_empty() { + ecs_world_t *world = ecs_init(); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + test_assert(s != NULL); + + ecs_snapshot_free(s); + + ecs_fini(world); +} + +void Snapshot_snapshot_free() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_assert( ecs_new(world, Position) != 0); + test_assert( ecs_new(world, Velocity) != 0); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + test_assert(s != NULL); + + ecs_snapshot_free(s); + + ecs_fini(world); +} + +void Snapshot_snapshot_free_filtered() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_assert( ecs_new(world, Position) != 0); + test_assert( ecs_new(world, Velocity) != 0); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + test_assert(s != NULL); + + ecs_snapshot_free(s); + + ecs_fini(world); +} + +void Snapshot_snapshot_free_filtered_w_dtor() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = "e1" + }); + + ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = "e2" + }); + + ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = "e3" + }); + + ecs_add(world, e1, Position); + ecs_add(world, e2, Position); + ecs_add(world, e3, Position); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + ecs_add(world, e3, Velocity); + + ecs_add(world, e3, Mass); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }, { ecs_id(Velocity) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + test_assert(s != NULL); + + ecs_snapshot_free(s); + + ecs_fini(world); +} + +static bool invoked = false; + +static +void Dummy(ecs_iter_t *it) { + invoked = true; +} + +void Snapshot_snapshot_activate_table_w_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {0, 0}); + test_assert(e != 0); + + ecs_filter_t f; + ecs_filter_init(world, &f, &(ecs_filter_desc_t) { + .terms = {{ ecs_id(Position) }} + }); + + ecs_iter_t it = ecs_filter_iter(world, &f); + ecs_snapshot_t *s = ecs_snapshot_take_w_iter(&it, ecs_filter_next); + + ecs_snapshot_restore(world, s); + + test_assert(ecs_has(world, e, Position)); + + ecs_progress(world, 0); + test_bool(invoked, true); + + /* Cleanup */ + ecs_fini(world); +} + +void Snapshot_snapshot_copy() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + ecs_iter_t it = ecs_snapshot_iter(s, NULL); + ecs_snapshot_t *s_copy = ecs_snapshot_take_w_iter(&it, ecs_snapshot_next); + ecs_snapshot_free(s); + + Position *p = ecs_get_mut(world, e, Position, NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p->x ++; + p->y ++; + + ecs_snapshot_restore(world, s_copy); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get_mut(world, e, Position, NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Snapshot_snapshot_get_ref_after_restore() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_ref_t ref = {0}; + const Position *p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + Position *p_mut = ecs_get_mut(world, e, Position, NULL); + test_int(p_mut->x, 10); + test_int(p_mut->y, 20); + + p_mut->x ++; + p_mut->y ++; + + ecs_snapshot_restore(world, s); + + test_assert(ecs_has(world, e, Position)); + p = ecs_get_ref(world, &ref, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void Snapshot_new_after_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + + ecs_add(world, e2, Velocity); + test_assert(ecs_has(world, e2, Velocity)); + + ecs_fini(world); +} + +void Snapshot_add_after_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + ecs_add(world, e, Velocity); + test_assert(ecs_has(world, e, Velocity)); + + ecs_fini(world); +} + +void Snapshot_delete_after_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + ecs_delete(world, e); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void Snapshot_new_empty_after_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + + ecs_fini(world); +} + +void Snapshot_set_after_snapshot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + + ecs_set_name(world, e2, "e2"); + test_assert(ecs_has_pair(world, e2, ecs_id(EcsIdentifier), EcsName)); + test_str(ecs_get_name(world, e2), "e2"); + + ecs_fini(world); +} + +void Snapshot_restore_recycled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_delete(world, e); + + ecs_snapshot_restore(world, s); + + ecs_entity_t e2 = ecs_new(world, 0); + test_assert(e2 != 0); + test_assert(e != e2); + + ecs_fini(world); +} + +static +void SetP(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + ecs_set_name(it->world, 0, "e2"); + } +} + +void Snapshot_snapshot_w_new_in_onset() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + + ECS_SYSTEM(world, SetP, EcsOnSet, Position); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + ecs_entity_t e2 = ecs_lookup(world, "e2"); + test_assert(e2 != 0); + + ecs_entity_t e3 = ecs_set_name(world, 0, "e3"); + test_assert(e3 != 0); + test_assert(e3 > e2); + test_str(ecs_get_name(world, e3), "e3"); + + ecs_fini(world); +} + +static ecs_entity_t v_entity = 0; + +static +void CreateV(ecs_iter_t *it) { + ecs_entity_t ecs_id(Velocity) = ecs_term_id(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + v_entity = ecs_set(it->world, 0, Velocity, {3, 4}); + } +} + +void Snapshot_snapshot_w_new_in_onset_in_snapshot_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e2 = ecs_set(world, 0, Velocity, {1, 2}); + + ECS_SYSTEM(world, CreateV, EcsOnSet, Position, :Velocity); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + ecs_snapshot_restore(world, s); + + const Velocity *v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + v = ecs_get(world, v_entity, Velocity); + test_assert(v != NULL); + test_int(v->x, 3); + test_int(v->y, 4); + + ecs_fini(world); +} + + +void Snapshot_snapshot_from_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {3, 4}); + + ecs_staging_begin(world); + + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_snapshot_t *s = ecs_snapshot_take(stage); + + ecs_delete(stage, e1); + ecs_delete(stage, e2); + + ecs_staging_end(world); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + + ecs_snapshot_restore(world, s); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 3); + test_int(p->y, 4); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Sorting.c b/fggl/ecs2/flecs/test/api/src/Sorting.c new file mode 100644 index 0000000000000000000000000000000000000000..2301a776fa8c86260845b1c0d6c470b45dd9a1fd --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Sorting.c @@ -0,0 +1,1491 @@ +#include <api.h> + +int compare_position( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + const Position *p1 = ptr1; + const Position *p2 = ptr2; + return (p1->x > p2->x) - (p1->x < p2->x); +} + +int compare_entity( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + return (e1 > e2) - (e1 < e2); +} + +void Sorting_sort_by_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {5, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {4, 0}); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 5); + + test_assert(it.entities[0] == e2); + test_assert(it.entities[1] == e4); + test_assert(it.entities[2] == e1); + test_assert(it.entities[3] == e5); + test_assert(it.entities[4] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_by_component_same_value_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {1, 0}); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 5); + + + test_assert(it.entities[0] == e5); + test_assert(it.entities[1] == e4); + test_assert(it.entities[2] == e3); + test_assert(it.entities[3] == e2); + test_assert(it.entities[4] == e1); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_by_component_same_value_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e6 = ecs_set(world, 0, Position, {1, 0}); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + + test_int(it.count, 6); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e6); + test_assert(it.entities[2] == e2); + test_assert(it.entities[3] == e1); + test_assert(it.entities[4] == e3); + test_assert(it.entities[5] == e5); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_by_component_2_tables() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {5, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {4, 0}); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e4); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e1); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e5); + test_assert(it.entities[1] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_by_component_3_tables() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {4, 0}); + ecs_entity_t e6 = ecs_set(world, 0, Position, {5, 0}); + ecs_entity_t e7 = ecs_set(world, 0, Position, {7, 0}); + + ecs_add(world, e5, Velocity); + ecs_add(world, e6, Mass); + ecs_add(world, e7, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e2); + test_assert(it.entities[1] == e4); + test_assert(it.entities[2] == e1); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e6); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e7); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + + +void Sorting_sort_by_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {5, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {4, 0}); + + ecs_add(world, e2, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, 0, compare_entity); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e1); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_after_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {5, 0}); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_add(world, e3, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_add(world, e1, Velocity); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_add(world, e2, Velocity); + ecs_add(world, e2, Mass); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {5, 0}); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_add(world, e2, Mass); + ecs_add(world, e3, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_remove(world, e1, Velocity); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_remove(world, e2, Mass); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e2); + test_assert(it.entities[1] == e4); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_remove(world, e2, Velocity); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {5, 0}); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_add(world, e2, Mass); + ecs_add(world, e3, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_delete(world, e1); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_delete(world, e2); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_after_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {5, 0}); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_add(world, e2, Mass); + ecs_add(world, e3, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_set(world, e1, Position, {7, 0}); + + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e1); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +static +void FlipP(ecs_iter_t *it) { + Position *p = ecs_term(it, Position, 1); + + int32_t i; + for (i = 0; i < it->count; i ++) { + float x = p[i].x; + p[i].x = p[i].y; + p[i].y = x; + } +} + +void Sorting_sort_after_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, FlipP, EcsOnUpdate, Position); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 5}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 6}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 1}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {5, 3}); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_add(world, e2, Mass); + ecs_add(world, e3, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position, [in] Velocity"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_progress(world, 0); + + /* First iteration, query will register monitor with table, so table is + * always marked dirty */ + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e4); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e5); + test_assert(it.entities[1] == e1); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_progress(world, 0); + + /* Second iteration, query now needs to check dirty admin to see if system + * updated component */ + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_after_query() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 5}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {6, 6}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 1}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {5, 3}); + + ecs_add(world, e1, Velocity); + ecs_add(world, e2, Velocity); + + ecs_add(world, e3, Velocity); + ecs_add(world, e4, Velocity); + ecs_add(world, e5, Velocity); + + ecs_add(world, e2, Mass); + ecs_add(world, e3, Mass); + + ecs_query_t *flip_q = ecs_query_new(world, "Position"); + ecs_query_t *q = ecs_query_new(world, "Position, [in] Velocity"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_iter_t qit = ecs_query_iter(flip_q); + while (ecs_query_next(&qit)) { + FlipP(&qit); + } + + /* First iteration, query will register monitor with table, so table is + * always marked dirty */ + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e4); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e5); + test_assert(it.entities[1] == e1); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + qit = ecs_query_iter(flip_q); + while (ecs_query_next(&qit)) { + FlipP(&qit); + } + + /* Second iteration, query now needs to check dirty admin to see if system + * updated component */ + it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e1); + test_assert(it.entities[2] == e5); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e3); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_by_component_move_pivot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {5, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {10, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {1, 0}); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 4); + + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e3); + test_assert(it.entities[2] == e1); + test_assert(it.entities[3] == e2); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_1000_entities() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + for (int i = 0; i < 1000; i ++) { + int32_t v = rand(); + ecs_set(world, 0, Position, {v}); + + int32_t x = 0; + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + test_assert(it.count == (i + 1)); + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + } + + ecs_fini(world); +} + +void Sorting_sort_1000_entities_w_duplicates() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + for (int i = 0; i < 500; i ++) { + int32_t v = rand(); + ecs_set(world, 0, Position, {v}); + ecs_set(world, 0, Position, {v}); + + int32_t x = 0; + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + test_assert(it.count == (i + 1) * 2); + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + } + + ecs_fini(world); +} + +void Sorting_sort_1000_entities_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_entity_t start = ecs_new(world, 0); + + for (int i = 0; i < 1000; i ++) { + int32_t v = rand(); + ecs_set(world, i + start, Position, {v}); + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + test_assert(it.count == (i + 1)); + + int32_t j, x = 0; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + } + + for (int i = 0; i < 1000; i ++) { + int32_t v = rand(); + ecs_set(world, i + start, Position, {v}); + } + + int32_t x = 0; + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + test_assert(it.count == 1000); + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + + ecs_fini(world); +} + +void Sorting_sort_1000_entities_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + for (int i = 0; i < 500; i ++) { + int32_t v = rand(); + ecs_set(world, 0, Position, {v}); + ecs_entity_t e = ecs_set(world, 0, Position, {v}); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0, x = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, (i + 1) * 2); + } + + ecs_fini(world); +} + +void Sorting_sort_1000_entities_2_types_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_entity_t start = ecs_new(world, 0); + + for (int i = 0; i < 1000; i ++) { + int32_t v = rand(); + ecs_set(world, i + start, Position, {v}); + + if (!(i % 2)) { + ecs_add(world, i + start, Velocity); + } + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + int32_t j, x = 0; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + } + + for (int i = 0; i < 1000; i ++) { + int32_t v = rand(); + ecs_set(world, i + start, Position, {v}); + } + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0, x = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, 1000); + + ecs_fini(world); +} + +void Sorting_sort_1000_entities_add_type_after_sort() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_entity_t start = ecs_new(world, 0); + + for (int i = 0; i < 500; i ++) { + int32_t v = rand(); + ecs_set(world, i + start, Position, {v}); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j, x = 0; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, i + 1); + } + + for (int i = 0; i < 500; i ++) { + int32_t v = rand(); + ecs_set(world, i + start, Position, {v}); + } + + for (int i = 0; i < 500; i ++) { + int32_t v = rand(); + ecs_set(world, i + start + 500, Position, {v}); + ecs_add(world, i + start + 500, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0, x = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, i + 500 + 1); + } + + ecs_fini(world); +} + +void Sorting_sort_1500_entities_3_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + for (int i = 0; i < 500; i ++) { + ecs_set(world, 0, Position, {rand()}); + ecs_entity_t e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + + e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Mass); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0, x = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, (i + 1) * 3); + } + + ecs_fini(world); +} + +void Sorting_sort_2000_entities_4_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + for (int i = 0; i < 500; i ++) { + ecs_set(world, 0, Position, {rand()}); + + ecs_entity_t e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + + e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Mass); + + e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + ecs_add(world, e, Mass); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0, x = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, (i + 1) * 4); + } + + ecs_fini(world); +} + +void Sorting_sort_shared_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t base_1 = ecs_set(world, 0, Position, {0, 0}); + ecs_entity_t base_2 = ecs_set(world, 0, Position, {3, 0}); + ecs_entity_t base_3 = ecs_set(world, 0, Position, {7, 0}); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {6, 0}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {1, 0}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {5, 0}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {2, 0}); + ecs_entity_t e5 = ecs_set(world, 0, Position, {4, 0}); + ecs_entity_t e6 = ecs_new_w_pair(world, EcsIsA, base_3); + ecs_entity_t e7 = ecs_new_w_pair(world, EcsIsA, base_2); + ecs_entity_t e8 = ecs_new_w_pair(world, EcsIsA, base_1); + ecs_entity_t e9 = ecs_new_w_pair(world, EcsIsA, base_1); + + ecs_query_t *q = ecs_query_new(world, "ANY:Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == base_1); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_assert(it.entities[0] == e8); + test_assert(it.entities[1] == e9); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 3); + test_assert(it.entities[0] == e2); + test_assert(it.entities[1] == e4); + test_assert(it.entities[2] == base_2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e7); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 4); + test_assert(it.entities[0] == e5); + test_assert(it.entities[1] == e3); + test_assert(it.entities[2] == e1); + test_assert(it.entities[3] == base_3); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == e6); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Sorting_sort_2_entities_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_set(world, 0, Position, {rand()}); + + ecs_entity_t e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j, x = 0; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, 2); + + ecs_fini(world); +} + +void Sorting_sort_3_entities_3_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_set(world, 0, Position, {rand()}); + + ecs_entity_t e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + + e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Mass); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j, x = 0; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Sorting_sort_3_entities_3_types_2() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + ecs_query_t *q = ecs_query_new(world, "Tag"); + ecs_query_order_by(world, q, 0, compare_entity); + + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_entity_t e3 = ecs_new(world, Tag); + + ecs_add_id(world, e1, Foo); + ecs_add_id(world, e2, Bar); + + ecs_iter_t it = ecs_query_iter(q); + ecs_entity_t e = 0; + int32_t count = 0; + while (ecs_query_next(&it)) { + count += it.count; + + int32_t i; + for (i = 0; i < it.count; i ++) { + test_assert(e < it.entities[i]); + e = it.entities[i]; + test_assert(e == e1 || e == e2 || e == e3); + } + } + + test_int(count, 3); + + ecs_fini(world); +} + +void Sorting_sort_4_entities_4_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_set(world, 0, Position, {rand()}); + + ecs_entity_t e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + + e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Mass); + + e = ecs_set(world, 0, Position, {rand()}); + ecs_add(world, e, Velocity); + ecs_add(world, e, Mass); + + ecs_iter_t it = ecs_query_iter(q); + int32_t count = 0; + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + + count += it.count; + + int32_t j, x = 0; + for (j = 0; j < it.count; j ++) { + test_assert(x <= p[j].x); + x = p[j].x; + } + } + + test_int(count, 4); + + ecs_fini(world); +} + +void Sorting_sort_w_tags_only() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{Tag}}, + .order_by = compare_entity + }); + + ecs_entity_t root = ecs_new_id(world); + ecs_entity_t e1 = ecs_new_w_pair(world, EcsChildOf, root); + ecs_entity_t e2 = ecs_new_w_pair(world, EcsChildOf, root); + + ecs_add(world, e2, Tag); + ecs_add(world, e1, Tag); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 2); + test_int(it.entities[0], e1); + test_int(it.entities[1], e2); + + ecs_fini(world); +} + +void Sorting_sort_childof_marked() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{Tag}}, + .order_by = compare_entity + }); + + ecs_entity_t root = ecs_new_id(world); + ecs_entity_t e1 = ecs_new_w_pair(world, EcsChildOf, root); + ecs_entity_t e2 = ecs_new_w_pair(world, EcsChildOf, e1); + ecs_entity_t e3 = ecs_new_w_pair(world, EcsChildOf, root); + + ecs_add(world, e3, Tag); + ecs_add(world, e1, Tag); + + // Trigger sorting + ecs_query_iter(q); + + ecs_delete(world, root); + + test_assert(!ecs_is_alive(world, root)); + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + + ecs_fini(world); +} + +void Sorting_sort_isa_marked() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{Tag}}, + .order_by = compare_entity + }); + + ecs_entity_t root = ecs_new_id(world); + ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, root); + ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, e1); + ecs_entity_t e3 = ecs_new_w_pair(world, EcsIsA, root); + + ecs_add(world, e3, Tag); + ecs_add(world, e1, Tag); + + // Trigger sorting + ecs_query_iter(q); + + ecs_delete(world, root); + + test_assert(!ecs_is_alive(world, root)); + test_int(1, ecs_vector_count(ecs_get_type(world, e1))); + test_assert(ecs_has(world, e1, Tag)); + test_int(1, ecs_vector_count(ecs_get_type(world, e2))); + test_assert(ecs_has_pair(world, e2, EcsIsA, e1)); + test_int(1, ecs_vector_count(ecs_get_type(world, e3))); + test_assert(ecs_has(world, e3, Tag)); + + ecs_fini(world); +} + +void Sorting_sort_relation_marked() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + ECS_TAG(world, Rel); + + ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .filter.terms = {{Tag}}, + .order_by = compare_entity + }); + + ecs_entity_t root = ecs_new_id(world); + ecs_entity_t e1 = ecs_new_w_pair(world, Rel, root); + ecs_entity_t e2 = ecs_new_w_pair(world, Rel, e1); + ecs_entity_t e3 = ecs_new_w_pair(world, Rel, root); + + ecs_add(world, e3, Tag); + ecs_add(world, e1, Tag); + + // Trigger sorting + ecs_query_iter(q); + + ecs_delete(world, root); + + test_assert(!ecs_is_alive(world, root)); + test_int(1, ecs_vector_count(ecs_get_type(world, e1))); + test_assert(ecs_has(world, e1, Tag)); + test_int(1, ecs_vector_count(ecs_get_type(world, e2))); + test_assert(ecs_has_pair(world, e2, Rel, e1)); + test_int(1, ecs_vector_count(ecs_get_type(world, e3))); + test_assert(ecs_has(world, e3, Tag)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Stresstests.c b/fggl/ecs2/flecs/test/api/src/Stresstests.c new file mode 100644 index 0000000000000000000000000000000000000000..7cd3fccd006a7783d0fe98fb7d25be0d5383b10c --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Stresstests.c @@ -0,0 +1,311 @@ +#include <api.h> + +void Stresstests_setup() { + bake_set_os_api(); + ecs_tracing_enable(-3); +} + +static +void add_random( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component) +{ + if (rand() % 2) { + if (!entity) { + entity = ecs_new_w_entity(world, component); + } else { + ecs_add_id(world, entity, component); + } + } +} + +static +void set_random( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t component, + void *ptr, + void *expect, + int32_t size) +{ + if (rand() % 2) { + ecs_set_id(world, entity, component, size, ptr); + } +} + +static +void Delete_above_1000(ecs_iter_t *it) { + int i; + + for (i = 0; i < it->count; i ++) { + if ((i + it->frame_offset) > 1000) { + ecs_delete(it->world, it->entities[i]); + } + } +} + +static +void Add_random(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + add_random(it->world, 0, ctx->component); + add_random(it->world, it->entities[i], ctx->component_2); + add_random(it->world, it->entities[i], ctx->component_3); + } +} + +static +void Set_velocity_callback(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + int i; + for (i = 0; i < it->count; i ++) { + v->x ++; + v->y ++; + } +} + +static +void Set_random(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + Position pos = {10, 20}; + set_random(it->world, 0, ctx->component, &pos, &pos, sizeof(Position)); + + Velocity vel = {30, 40}; + Velocity vel_expect = {31, 41}; + set_random(it->world, it->entities[i], ctx->component_2, &vel, &vel_expect, + sizeof(Velocity)); + + Rotation rot = {50}; + set_random(it->world, it->entities[i], ctx->component_3, &rot, &rot, + sizeof(Rotation)); + } +} + +static +void create_delete_entity_random_components_staged( + int32_t threads) +{ + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Add_random, EcsOnUpdate, Position); + ECS_SYSTEM(world, Delete_above_1000, EcsPostUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity), .component_3 = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + ecs_bulk_new(world, Position, 500); + ecs_bulk_new(world, Type, 500); + + if (threads) { + ecs_set_threads(world, threads); + } + + uint64_t i; + for (i = 0; i < 100; i ++) { + ecs_progress(world, 1); + } + + test_assert(true); + + ecs_fini(world); +} + +static +void set_entity_random_components( + int32_t threads) +{ + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Set_random, EcsOnUpdate, Position); + ECS_SYSTEM(world, Set_velocity_callback, EcsOnSet, Velocity); + ECS_SYSTEM(world, Delete_above_1000, EcsPostUpdate, Position); + + IterData ctx = {.component = ecs_id(Position), .component_2 = ecs_id(Velocity), .component_3 = ecs_id(Rotation)}; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 5); + test_assert(ids != NULL); + + ids = ecs_bulk_new(world, Type, 5); + test_assert(ids != NULL); + + if (threads) { + ecs_set_threads(world, threads); + } + + int i; + for (i = 0; i < 100; i ++) { + ecs_progress(world, 1); + } + + ecs_fini(world); +} + +void Stresstests_create_delete_entity_random_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + + const ecs_entity_t *ids = ecs_bulk_new(world, 0, 1000); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 1000; i ++) { + add_random(world, ids[i], ecs_id(Position)); + add_random(world, ids[i], ecs_id(Velocity)); + add_random(world, ids[i], ecs_id(Rotation)); + } + + ecs_fini(world); +} + +void Stresstests_set_entity_random_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + + ECS_SYSTEM(world, Set_velocity_callback, EcsOnSet, Velocity); + + const ecs_entity_t *ids = ecs_bulk_new(world, 0, 1000); + test_assert(ids != NULL); + + int i; + for (i = 0; i < 1000; i ++) { + Position pos = {10, 20}; + set_random(world, ids[i], ecs_id(Position), &pos, &pos, sizeof(Position)); + + Velocity vel = {30, 40}; + Velocity vel_expect = {31, 41}; + set_random(world, ids[i], ecs_id(Velocity), &vel, &vel_expect, sizeof(Velocity)); + + Rotation rot = {50}; + set_random(world, ids[i], ecs_id(Rotation), &rot, &rot, sizeof(Rotation)); + } + + ecs_fini(world); +} + +void Stresstests_create_delete_entity_random_components_staged() { + create_delete_entity_random_components_staged(0); +} + +void Stresstests_set_entity_random_components_staged() { + set_entity_random_components(0); +} + +void Stresstests_create_delete_entity_random_components_2_threads() { + create_delete_entity_random_components_staged(2); +} + +void Stresstests_set_entity_random_components_2_threads() { + set_entity_random_components(2); +} + +void Stresstests_create_delete_entity_random_components_6_threads() { + create_delete_entity_random_components_staged(6); +} + +void Stresstests_set_entity_random_components_6_threads() { + set_entity_random_components(6); +} + +void Stresstests_create_delete_entity_random_components_12_threads() { + create_delete_entity_random_components_staged(12); +} + +void Stresstests_set_entity_random_components_12_threads() { + set_entity_random_components(12); +} + +void Stresstests_create_2m_entities_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + int32_t i; + for (i = 0; i < 2000 * 1000; i ++) { + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(ecs_has(world, e, Position)); + } + + ecs_fini(world); +} + +void Stresstests_create_2m_entities_bulk_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 2000 * 1000); + test_assert(ids != NULL); + + int32_t i; + for (i = 0; i < 2000 * 1000; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + } + + ecs_fini(world); +} + +void Stresstests_add_1k_tags() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + + int i; + for (i = 0; i < 1000; i ++) { + ecs_add_id(world, e, i + 1000); + test_assert(ecs_has_entity(world, e, i + 1000)); + } + + ecs_type_t type = ecs_get_type(world, e); + test_assert(type != NULL); + test_int(ecs_vector_count(type), 1000); + + ecs_fini(world); +} + +void Stresstests_create_1m_set_two_components() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + ecs_entity_t i, j; + for (i = e; i < 1000000 + e; i ++) { + ecs_set(world, i, Position, {10, 20}); + } + + for (j = e; j < 1000000 + e; j ++) { + test_assert(ecs_get_type(world, j) != NULL); + } + + for (i = e; i < e + 1000000 + e; i ++) { + ecs_set(world, i, Velocity, {1, 2}); + } + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Switch.c b/fggl/ecs2/flecs/test/api/src/Switch.c new file mode 100644 index 0000000000000000000000000000000000000000..e3c16e7e154c6c07c80599e609fd47bb4616b397 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Switch.c @@ -0,0 +1,1142 @@ +#include <api.h> + +void Switch_setup() { + ecs_tracing_enable(-3); +} + +void Switch_get_case_empty() { + ecs_world_t *world = ecs_init(); + + ECS_TYPE(world, Type, 0); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_entity_t case_id = ecs_get_case(world, e, Type); + test_int(case_id, 0); + + ecs_fini(world); +} + +void Switch_get_case_no_switch() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TYPE(world, Type, 0); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_entity_t case_id = ecs_get_case(world, e, Type); + test_int(case_id, 0); + + ecs_fini(world); +} + +void Switch_get_case_unset() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Type, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new_w_entity(world, ECS_SWITCH | Type); + test_assert(e != 0); + + ecs_entity_t case_id = ecs_get_case(world, e, Type); + test_int(case_id, 0); + + ecs_fini(world); +} + +void Switch_get_case_set() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Type, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new_w_entity(world, ECS_SWITCH | Type); + test_assert(e != 0); + + ecs_add_id(world, e, ECS_CASE | Walking); + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + + ecs_entity_t case_id = ecs_get_case(world, e, Type); + test_int(case_id, Walking); + + ecs_fini(world); +} + +void Switch_get_case_change() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Type, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new_w_entity(world, ECS_SWITCH | Type); + test_assert(e != 0); + + ecs_add_id(world, e, ECS_CASE | Walking); + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + + ecs_add_id(world, e, ECS_CASE | Running); + test_assert( !ecs_has_entity(world, e, ECS_CASE | Walking)); + test_assert( ecs_has_entity(world, e, ECS_CASE | Running)); + + ecs_entity_t case_id = ecs_get_case(world, e, Type); + test_int(case_id, Running); + + ecs_fini(world); +} + +void Switch_remove_case() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e != 0); + + ecs_add_id(world, e, ECS_CASE | Walking); + + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + ecs_entity_t case_id = ecs_get_case(world, e, Movement); + test_int(case_id, Walking); + + ecs_remove_id(world, e, ECS_CASE | Walking); + + test_assert( !ecs_has_entity(world, e, ECS_CASE | Walking)); + case_id = ecs_get_case(world, e, Movement); + test_int(case_id, 0); + + ecs_fini(world); +} + +void Switch_remove_last() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Walking, Running); + ECS_TAG(world, Tag); + + ecs_entity_t e1 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e1 != 0); + ecs_add_id(world, e1, ECS_CASE | Walking); + ecs_add(world, e1, Tag); + + ecs_entity_t e2 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e2 != 0); + ecs_add_id(world, e2, ECS_CASE | Walking); + ecs_add(world, e2, Tag); + + ecs_remove(world, e2, Tag); + + test_assert(!ecs_has(world, e2, Tag)); + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + + test_assert(ecs_has(world, e1, Tag)); + test_assert(ecs_has_entity(world, e1, ECS_CASE | Walking)); + + ecs_fini(world); +} + +void Switch_delete_first() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Walking, Running); + + ecs_query_t *q = ecs_query_new(world, "CASE | Walking"); + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e1 != 0); + ecs_add_id(world, e1, ECS_CASE | Walking); + + ecs_entity_t e2 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e2 != 0); + ecs_add_id(world, e2, ECS_CASE | Walking); + + ecs_entity_t e3 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e3 != 0); + ecs_add_id(world, e3, ECS_CASE | Walking); + + ecs_delete(world, e1); + + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e3, ECS_CASE | Walking)); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e3); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e2); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Switch_delete_last() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Walking, Running); + + ecs_query_t *q = ecs_query_new(world, "CASE | Walking"); + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e1 != 0); + ecs_add_id(world, e1, ECS_CASE | Walking); + + ecs_entity_t e2 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e2 != 0); + ecs_add_id(world, e2, ECS_CASE | Walking); + + ecs_entity_t e3 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e3 != 0); + ecs_add_id(world, e3, ECS_CASE | Walking); + + ecs_delete(world, e3); + + test_assert(ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e2); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e1); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Switch_delete_first_last() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Walking, Running); + + ecs_query_t *q = ecs_query_new(world, "CASE | Walking"); + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e1 != 0); + ecs_add_id(world, e1, ECS_CASE | Walking); + + ecs_entity_t e2 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e2 != 0); + ecs_add_id(world, e2, ECS_CASE | Walking); + + ecs_entity_t e3 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e3 != 0); + ecs_add_id(world, e3, ECS_CASE | Walking); + + ecs_delete(world, e1); + ecs_delete(world, e3); + + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e2); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Switch_3_entities_same_case() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e3, SWITCH | Movement, CASE | Running); + + test_assert( ecs_has_entity(world, e1, ECS_CASE | Running)); + test_assert( ecs_has_entity(world, e2, ECS_CASE | Running)); + test_assert( ecs_has_entity(world, e3, ECS_CASE | Running)); + + test_int(ecs_get_case(world, e1, Movement), Running); + test_int(ecs_get_case(world, e2, Movement), Running); + test_int(ecs_get_case(world, e3, Movement), Running); + + ecs_fini(world); +} + +void Switch_2_entities_1_change_case() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Running); + + ecs_add_id(world, e2, ECS_CASE | Jumping); + test_assert( ecs_has_entity(world, e1, ECS_CASE | Running)); + test_assert( ecs_has_entity(world, e2, ECS_CASE | Jumping)); + + test_int(ecs_get_case(world, e1, Movement), Running); + test_int(ecs_get_case(world, e2, Movement), Jumping); + + ecs_fini(world); +} + +void Switch_3_entities_change_case() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e3, SWITCH | Movement, CASE | Running); + + ecs_add_id(world, e1, ECS_CASE | Walking); + test_assert( ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert( ecs_has_entity(world, e2, ECS_CASE | Running)); + test_assert( ecs_has_entity(world, e3, ECS_CASE | Running)); + + test_int(ecs_get_case(world, e1, Movement), Walking); + test_int(ecs_get_case(world, e2, Movement), Running); + test_int(ecs_get_case(world, e3, Movement), Running); + + ecs_add_id(world, e2, ECS_CASE | Jumping); + test_assert( ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert( ecs_has_entity(world, e2, ECS_CASE | Jumping)); + test_assert( ecs_has_entity(world, e3, ECS_CASE | Running)); + + test_int(ecs_get_case(world, e1, Movement), Walking); + test_int(ecs_get_case(world, e2, Movement), Jumping); + test_int(ecs_get_case(world, e3, Movement), Running); + + ecs_add_id(world, e3, ECS_CASE | Walking); + test_assert( ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert( ecs_has_entity(world, e2, ECS_CASE | Jumping)); + test_assert( ecs_has_entity(world, e3, ECS_CASE | Walking)); + + test_int(ecs_get_case(world, e1, Movement), Walking); + test_int(ecs_get_case(world, e2, Movement), Jumping); + test_int(ecs_get_case(world, e3, Movement), Walking); + + ecs_fini(world); +} + +static +void MatchSwitch(ecs_iter_t *it) { + ecs_entity_t *movement = ecs_term(it, ecs_entity_t, 1); + test_assert(movement != NULL); + probe_system(it); +} + +void Switch_query_switch() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, MatchSwitch, EcsOnUpdate, SWITCH | Movement); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e3, SWITCH | Movement, CASE | Running); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, MatchSwitch); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ECS_SWITCH | Movement); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void Switch_query_1_case_1_type() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, MatchSwitch, EcsOnUpdate, CASE | Running); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e3, SWITCH | Movement, CASE | Running); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, MatchSwitch); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.e[1], e1); + test_int(ctx.c[0][0], ECS_CASE | Running); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void Switch_query_1_case_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, MatchSwitch, EcsOnUpdate, CASE | Running); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e3, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e4, SWITCH | Movement, CASE | Walking, Position); + ECS_ENTITY(world, e5, SWITCH | Movement, CASE | Running, Position); + ECS_ENTITY(world, e6, SWITCH | Movement, CASE | Walking, Position); + ECS_ENTITY(world, e7, SWITCH | Movement, CASE | Running, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 4); + test_int(ctx.system, MatchSwitch); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.e[1], e1); + test_int(ctx.e[2], e7); + test_int(ctx.e[3], e5); + test_int(ctx.c[0][0], ECS_CASE | Running); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void Switch_query_2_cases_1_type() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_TAG(world, Front); + ECS_TAG(world, Back); + ECS_TAG(world, Left); + ECS_TAG(world, Right); + ECS_TYPE(world, Direction, Front, Back, Left, Right); + + ECS_SYSTEM(world, MatchSwitch, EcsOnUpdate, CASE | Running, CASE | Front); + + ECS_ENTITY(world, e1, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e2, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e3, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Back); + ECS_ENTITY(world, e4, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e5, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, MatchSwitch); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e4); + test_int(ctx.e[1], e1); + test_int(ctx.c[0][0], ECS_CASE | Running); + test_int(ctx.c[0][1], ECS_CASE | Front); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void Switch_query_2_cases_2_types() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_TAG(world, Front); + ECS_TAG(world, Back); + ECS_TAG(world, Left); + ECS_TAG(world, Right); + ECS_TYPE(world, Direction, Front, Back, Left, Right); + + ECS_SYSTEM(world, MatchSwitch, EcsOnUpdate, CASE | Running, CASE | Front); + + ECS_ENTITY(world, e1, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e2, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e3, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Back); + ECS_ENTITY(world, e4, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e5, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e6, + Position, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e7, + Position, + SWITCH | Movement, CASE | Running, + SWITCH | Direction, CASE | Front); + ECS_ENTITY(world, e8, + Position, + SWITCH | Movement, CASE | Walking, + SWITCH | Direction, CASE | Front); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 3); + test_int(ctx.system, MatchSwitch); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e4); + test_int(ctx.e[1], e1); + test_int(ctx.e[2], e7); + test_int(ctx.c[0][0], ECS_CASE | Running); + test_int(ctx.c[0][1], ECS_CASE | Front); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void Switch_query_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_ENTITY(world, e1, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e2, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e3, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e4, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e5, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e6, SWITCH | Movement, CASE | Jumping); + ECS_ENTITY(world, e7, SWITCH | Movement, CASE | Jumping); + + ecs_query_t *q_walking = ecs_query_new(world, "CASE | Walking"); + ecs_query_t *q_running = ecs_query_new(world, "CASE | Running"); + ecs_query_t *q_jumping = ecs_query_new(world, "CASE | Jumping"); + + /* Verify all queries are correctly matched */ + ecs_iter_t it = ecs_query_iter(q_walking); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e2); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e1); + test_assert(!ecs_query_next(&it)); + + it = ecs_query_iter(q_running); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e5); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e4); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e3); + test_assert(!ecs_query_next(&it)); + + it = ecs_query_iter(q_jumping); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e7); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e6); + test_assert(!ecs_query_next(&it)); + + ecs_remove_id(world, e4, ECS_CASE | Running); + test_assert(!ecs_has_entity(world, e4, ECS_CASE | Running)); + ecs_entity_t c = ecs_get_case(world, e4, Movement); + test_int(c, 0); + + /* Verify queries are still correctly matched, now excluding e4 */ + it = ecs_query_iter(q_walking); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e2); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e1); + test_assert(!ecs_query_next(&it)); + + it = ecs_query_iter(q_running); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e5); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e3); + test_assert(!ecs_query_next(&it)); + + it = ecs_query_iter(q_jumping); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e7); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e6); + test_assert(!ecs_query_next(&it)); + + ecs_add_id(world, e4, ECS_CASE | Running); + test_assert(ecs_has_entity(world, e4, ECS_CASE | Running)); + c = ecs_get_case(world, e4, Movement); + test_int(c, Running); + + /* Verify e4 is now matched again */ + it = ecs_query_iter(q_walking); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e2); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e1); + test_assert(!ecs_query_next(&it)); + + it = ecs_query_iter(q_running); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e4); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e5); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e3); + test_assert(!ecs_query_next(&it)); + + it = ecs_query_iter(q_jumping); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e7); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); test_int(it.entities[0], e6); + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +static +void AddSwitch(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_entity_t movement = ecs_term_id(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_id(world, it->entities[i], ECS_SWITCH | movement); + } +} + +void Switch_add_switch_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, AddSwitch, EcsOnUpdate, Position, :Movement); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + + ecs_progress(world, 0); + + test_assert(ecs_has_entity(world, e1, ECS_SWITCH | Movement)); + test_assert(ecs_has_entity(world, e2, ECS_SWITCH | Movement)); + test_assert(ecs_has_entity(world, e3, ECS_SWITCH | Movement)); + + ecs_fini(world); +} + +static +void SetCase(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_entity_t sw_case = ecs_term_id(it, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_id(world, it->entities[i], ECS_CASE | sw_case); + } +} + +void Switch_add_case_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, SetCase, EcsOnUpdate, Position, :Walking); + + ECS_ENTITY(world, e1, Position, SWITCH | Movement); + ECS_ENTITY(world, e2, Position, SWITCH | Movement); + ECS_ENTITY(world, e3, Position, SWITCH | Movement); + + ecs_progress(world, 0); + + test_assert(ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e3, ECS_CASE | Walking)); + + ecs_fini(world); +} + +void Switch_change_case_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, SetCase, EcsOnUpdate, Position, :Walking); + + ECS_ENTITY(world, e1, Position, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e2, Position, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e3, Position, SWITCH | Movement, CASE | Running); + + ecs_progress(world, 0); + + test_assert(!ecs_has_entity(world, e1, ECS_CASE | Running)); + test_assert(!ecs_has_entity(world, e2, ECS_CASE | Running)); + test_assert(!ecs_has_entity(world, e3, ECS_CASE | Running)); + + test_assert(ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e3, ECS_CASE | Walking)); + + ecs_fini(world); +} + +void Switch_change_one_case_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, SetCase, EcsOnUpdate, Position, :Jumping, CASE | Walking); + + ECS_ENTITY(world, e0, Position, SWITCH | Movement, CASE | Jumping); + ECS_ENTITY(world, e1, Position, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e2, Position, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e3, Position, SWITCH | Movement, CASE | Walking); + ECS_ENTITY(world, e4, Position, SWITCH | Movement, CASE | Running); + ECS_ENTITY(world, e5, Position, SWITCH | Movement, CASE | Jumping); + + ecs_progress(world, 0); + + test_assert(ecs_has_entity(world, e0, ECS_CASE | Jumping)); + test_assert(ecs_has_entity(world, e1, ECS_CASE | Jumping)); + test_assert(ecs_has_entity(world, e2, ECS_CASE | Running)); + test_assert(ecs_has_entity(world, e3, ECS_CASE | Jumping)); + test_assert(ecs_has_entity(world, e4, ECS_CASE | Running)); + test_assert(ecs_has_entity(world, e5, ECS_CASE | Jumping)); + + ecs_fini(world); +} + +static +void RemoveSwitch(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_entity_t movement = ecs_term_id(it, 1); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_remove_id(world, it->entities[i], ECS_SWITCH | movement); + } +} + +void Switch_remove_switch_in_stage() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ECS_SYSTEM(world, RemoveSwitch, EcsOnUpdate, SWITCH | Movement); + + ECS_ENTITY(world, e1, Position, SWITCH | Movement); + ECS_ENTITY(world, e2, Position, SWITCH | Movement); + ECS_ENTITY(world, e3, Position, SWITCH | Movement); + + ecs_progress(world, 0); + + test_assert(!ecs_has_entity(world, e1, ECS_SWITCH | Movement)); + test_assert(!ecs_has_entity(world, e2, ECS_SWITCH | Movement)); + test_assert(!ecs_has_entity(world, e3, ECS_SWITCH | Movement)); + + ecs_fini(world); +} + +void Switch_switch_no_match_for_case() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TYPE(world, Movement, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_id(world, e, ECS_SWITCH | Movement); + ecs_add_id(world, e, ECS_CASE | Walking); + + ecs_query_t *q = ecs_query_new(world, "CASE | Running"); + ecs_iter_t it = ecs_query_iter(q); + + int count = 0; + while (ecs_query_next(&it)) { + count ++; + } + + test_assert(count == 0); + + ecs_fini(world); +} + +void Switch_empty_entity_has_case() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(!ecs_has_entity(world, e, ECS_CASE | Walking)); + + ecs_fini(world); +} + +void Switch_zero_entity_has_case() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + + test_expect_abort(); + + test_assert(!ecs_has_entity(world, 0, ECS_CASE | Walking)); +} + +void Switch_add_to_entity_w_switch() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_COMPONENT(world, Position); + + ECS_TYPE(world, Type, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new_w_entity(world, ECS_SWITCH | Type); + test_assert(e != 0); + + ecs_add_id(world, e, ECS_CASE | Walking); + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + test_int(ecs_get_case(world, e, Type), Walking); + + ecs_add(world, e, Position); + test_assert(ecs_has(world, e, Position)); + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + test_int(ecs_get_case(world, e, Type), Walking); + + ecs_fini(world); +} + +void Switch_add_pair_to_entity_w_switch() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + ECS_TYPE(world, Type, Walking, Running, Jumping); + + ecs_entity_t e = ecs_new_w_entity(world, ECS_SWITCH | Type); + test_assert(e != 0); + + ecs_add_id(world, e, ECS_CASE | Walking); + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + test_int(ecs_get_case(world, e, Type), Walking); + + ecs_entity_t pair_id = ecs_pair(ecs_id(Position), Pair); + ecs_add_id(world, e, pair_id); + test_assert(ecs_has_entity(world, e, pair_id)); + test_assert( ecs_has_entity(world, e, ECS_CASE | Walking)); + test_int(ecs_get_case(world, e, Type), Walking); + + ecs_fini(world); +} + +static +int compare_position(ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2) { + const Position *p1 = ptr1; + const Position *p2 = ptr2; + return (p1->x > p2->x) - (p1->x < p2->x); +} + +void Switch_sort() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TAG(world, Jumping); + ECS_TAG(world, Sitting); + ECS_TYPE(world, Type, Walking, Running, Jumping, Sitting); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {3, 2}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {2, 2}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {1, 2}); + ecs_entity_t e4 = ecs_set(world, 0, Position, {0, 2}); + + ecs_add_id(world, e1, ECS_SWITCH | Type); + ecs_add_id(world, e2, ECS_SWITCH | Type); + ecs_add_id(world, e3, ECS_SWITCH | Type); + ecs_add_id(world, e4, ECS_SWITCH | Type); + + ecs_add_id(world, e1, ECS_CASE | Walking); + ecs_add_id(world, e2, ECS_CASE | Running); + ecs_add_id(world, e3, ECS_CASE | Jumping); + ecs_add_id(world, e4, ECS_CASE | Sitting); + + ecs_query_t *q = ecs_query_new(world, "Position"); + ecs_query_order_by(world, q, ecs_id(Position), compare_position); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 4); + test_assert(it.entities[0] == e4); + test_assert(it.entities[1] == e3); + test_assert(it.entities[2] == e2); + test_assert(it.entities[3] == e1); + + /* Entities will have shuffled around, ensure cases got shuffled too */ + test_int(ecs_get_case(world, e1, Type), Walking); + test_int(ecs_get_case(world, e2, Type), Running); + test_int(ecs_get_case(world, e3, Type), Jumping); + test_int(ecs_get_case(world, e4, Type), Sitting); + + ecs_fini(world); +} + + +void Switch_recycled_tags() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_delete(world, e1); + ecs_delete(world, e2); + ecs_delete(world, e3); + + ECS_TAG(world, Standing); + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Standing, Walking, Running); + + test_assert(Standing > UINT32_MAX); + test_assert(Walking > UINT32_MAX); + test_assert(Running > UINT32_MAX); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_id(world, e, ECS_SWITCH | Movement); + ecs_add_id(world, e, ECS_CASE | Standing); + + test_assert(ecs_has_entity(world, e, ECS_SWITCH | Movement)); + test_assert(ecs_has_entity(world, e, ECS_CASE | Standing)); + + ecs_fini(world); +} + +void Switch_query_recycled_tags() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new_id(world); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_delete(world, e1); + ecs_delete(world, e2); + ecs_delete(world, e3); + + ECS_TAG(world, Standing); + ECS_TAG(world, Walking); + ECS_TAG(world, Running); + ECS_TYPE(world, Movement, Standing, Walking, Running); + + test_assert(Standing > UINT32_MAX); + test_assert(Walking > UINT32_MAX); + test_assert(Running > UINT32_MAX); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add_id(world, e, ECS_SWITCH | Movement); + ecs_add_id(world, e, ECS_CASE | Standing); + + ecs_query_t *q = ecs_query_new(world, "SWITCH | Movement, CASE | Standing"); + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e); + + ecs_entity_t *cases = ecs_term(&it, ecs_entity_t, 1); + test_assert(cases != NULL); + test_assert(cases[0] == Standing); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void Switch_single_case() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Walking); + ECS_TYPE(world, Movement, Walking); + + ecs_query_t *q = ecs_query_new(world, "CASE | Walking"); + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e1 != 0); + ecs_add_id(world, e1, ECS_CASE | Walking); + + ecs_entity_t e2 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e2 != 0); + ecs_add_id(world, e2, ECS_CASE | Walking); + + ecs_entity_t e3 = ecs_new_w_entity(world, ECS_SWITCH | Movement); + test_assert(e3 != 0); + ecs_add_id(world, e3, ECS_CASE | Walking); + + test_assert(ecs_has_entity(world, e1, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e3, ECS_CASE | Walking)); + + ecs_delete(world, e1); + + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + test_assert(ecs_has_entity(world, e3, ECS_CASE | Walking)); + + ecs_delete(world, e3); + + test_assert(ecs_has_entity(world, e2, ECS_CASE | Walking)); + + ecs_iter_t it = ecs_query_iter(q); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], e2); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemCascade.c b/fggl/ecs2/flecs/test/api/src/SystemCascade.c new file mode 100644 index 0000000000000000000000000000000000000000..5e7d30e8b4c44d616ffaafa952f259ec659ea6e5 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemCascade.c @@ -0,0 +1,792 @@ +#include <api.h> + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + Position *p_parent = ecs_term(it, Position, 2); + + test_assert(!p_parent || !ecs_is_owned(it, 2)); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x ++; + p[i].y ++; + + if (p_parent) { + p[i].x += p_parent->x; + p[i].y += p_parent->y; + } + } +} + +void SystemCascade_cascade_depth_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, CASCADE:Position); + + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + + ecs_add_pair(world, e3, EcsChildOf, e1); + ecs_add_pair(world, e4, EcsChildOf, e2); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + probe_has_entity(&ctx, e1); + probe_has_entity(&ctx, e2); + probe_has_entity(&ctx, e3); + probe_has_entity(&ctx, e4); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + ecs_fini(world); +} + +void SystemCascade_cascade_depth_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + ECS_ENTITY(world, e6, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, CASCADE:Position); + + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + ecs_set(world, e5, Position, {1, 2}); + ecs_set(world, e6, Position, {1, 2}); + + ecs_add_pair(world, e3, EcsChildOf, e1); /* depth 1 */ + ecs_add_pair(world, e4, EcsChildOf, e2); /* depth 1 */ + ecs_add_pair(world, e5, EcsChildOf, e3); /* depth 2 */ + ecs_add_pair(world, e6, EcsChildOf, e4); /* depth 2 */ + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 6); + test_int(ctx.invoked, 5); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_assert((ctx.e[0] == e1 && ctx.e[1] == e2) || (ctx.e[0] == e2 && ctx.e[1] == e1)); + test_assert((ctx.e[2] == e3 && ctx.e[3] == e4) || (ctx.e[2] == e4 && ctx.e[3] == e3)); + test_assert((ctx.e[4] == e5 && ctx.e[5] == e6) || (ctx.e[4] == e6 && ctx.e[5] == e5)); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e5, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 9); + + p = ecs_get(world, e6, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 9); + + ecs_fini(world); +} + +void SystemCascade_cascade_depth_2_new_syntax() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + ECS_ENTITY(world, e6, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Position(cascade(ChildOf))); + + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + ecs_set(world, e5, Position, {1, 2}); + ecs_set(world, e6, Position, {1, 2}); + + ecs_add_pair(world, e3, EcsChildOf, e1); /* depth 1 */ + ecs_add_pair(world, e4, EcsChildOf, e2); /* depth 1 */ + ecs_add_pair(world, e5, EcsChildOf, e3); /* depth 2 */ + ecs_add_pair(world, e6, EcsChildOf, e4); /* depth 2 */ + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 6); + test_int(ctx.invoked, 5); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_assert((ctx.e[0] == e1 && ctx.e[1] == e2) || (ctx.e[0] == e2 && ctx.e[1] == e1)); + test_assert((ctx.e[2] == e3 && ctx.e[3] == e4) || (ctx.e[2] == e4 && ctx.e[3] == e3)); + test_assert((ctx.e[4] == e5 && ctx.e[5] == e6) || (ctx.e[4] == e6 && ctx.e[5] == e5)); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e5, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 9); + + p = ecs_get(world, e6, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 9); + + ecs_fini(world); +} + +static +void AddParent(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + Position *p_parent = ecs_term(it, Position, 2); + + test_assert(!p_parent || !ecs_is_owned(it, 2)); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + if (p_parent) { + p[i].x += p_parent->x; + p[i].y += p_parent->y; + } + } +} + +void SystemCascade_add_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, AddParent, EcsOnUpdate, Position, CASCADE:Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + + ecs_add_pair(world, e3, EcsChildOf, parent); /* depth 1 */ + ecs_add_pair(world, e4, EcsChildOf, parent); /* depth 1 */ + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + /* Before adding Position to parent, it wasn't being considered for the + * CASCADE column, so tables could have been ordered randomly. Make sure + * that queries can handle changes to depth after all tables are matched */ + ecs_set(world, parent, Position, {1, 2}); + + ctx = (Probe){0}; + + ecs_progress(world, 1); + + test_int(ctx.count, 5); + test_int(ctx.invoked, 3); + test_int(ctx.system, AddParent); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_assert(ctx.e[0] == e1 || ctx.e[1] == e1 || ctx.e[2] == e1); + test_assert(ctx.e[0] == e2 || ctx.e[1] == e2 || ctx.e[2] == e2); + test_assert(ctx.e[0] == parent || ctx.e[1] == parent || ctx.e[2] == parent); + test_assert(ctx.e[3] == e3 || ctx.e[4] == e3); + test_assert(ctx.e[3] == e4 || ctx.e[4] == e4); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + ecs_fini(world); +} + +void SystemCascade_adopt_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, AddParent, EcsOnUpdate, Position, CASCADE:Position); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + ecs_add_pair(world, e3, EcsChildOf, parent); + ecs_add_pair(world, e4, EcsChildOf, parent); + + ctx = (Probe){0}; + + ecs_progress(world, 1); + + test_int(ctx.count, 5); + test_int(ctx.invoked, 3); + test_int(ctx.system, AddParent); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], parent); + test_int(ctx.e[3], e3); + test_int(ctx.e[4], e4); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + ecs_fini(world); +} + +void SystemCascade_rematch_w_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "CASCADE:Position, Velocity"); + test_assert(q != NULL); + + ecs_entity_t parent = ecs_new(world, Position); + ecs_add(world, parent, Velocity); + + ecs_entity_t child = ecs_new(world, Velocity); + ecs_add_pair(world, child, EcsChildOf, parent); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], parent); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], child); + test_assert(!ecs_query_next(&it)); + + // Change parent, trigger rematch + ecs_remove(world, parent, Position); + + it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], parent); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], child); + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void SystemCascade_query_w_only_cascade() { + ecs_world_t *world = ecs_mini(); + + ecs_query_t *q = ecs_query_new(world, "CASCADE:Name");; + test_assert(q != NULL); + + int32_t count = 0; + + /* Should match everything (since everything is a root without further + * qualifications). Since no other entities have been created, all entities + * must be builtin ones. */ + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + for (int i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + test_assert( + e == EcsFlecs || + ecs_has_pair(world, e, EcsChildOf, EcsFlecs) || + ecs_has_pair(world, e, EcsChildOf, EcsFlecsCore) + ); + + count ++; + } + } + + test_assert(count != 0); + + ecs_fini(world); +} + +void SystemCascade_custom_relation_cascade_depth_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Position(cascade(Rel))); + + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + + ecs_add_pair(world, e3, Rel, e1); + ecs_add_pair(world, e4, Rel, e2); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + probe_has_entity(&ctx, e1); + probe_has_entity(&ctx, e2); + probe_has_entity(&ctx, e3); + probe_has_entity(&ctx, e4); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + ecs_fini(world); +} + +void SystemCascade_custom_relation_cascade_depth_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + ECS_ENTITY(world, e6, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Position(cascade(Rel))); + + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + ecs_set(world, e5, Position, {1, 2}); + ecs_set(world, e6, Position, {1, 2}); + + ecs_add_pair(world, e3, Rel, e1); /* depth 1 */ + ecs_add_pair(world, e4, Rel, e2); /* depth 1 */ + ecs_add_pair(world, e5, Rel, e3); /* depth 2 */ + ecs_add_pair(world, e6, Rel, e4); /* depth 2 */ + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 6); + test_int(ctx.invoked, 5); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_assert((ctx.e[0] == e1 && ctx.e[1] == e2) || (ctx.e[0] == e2 && ctx.e[1] == e1)); + test_assert((ctx.e[2] == e3 && ctx.e[3] == e4) || (ctx.e[2] == e4 && ctx.e[3] == e3)); + test_assert((ctx.e[4] == e5 && ctx.e[5] == e6) || (ctx.e[4] == e6 && ctx.e[5] == e5)); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 4); + test_int(p->y, 6); + + p = ecs_get(world, e5, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 9); + + p = ecs_get(world, e6, Position); + test_assert(p != NULL); + test_int(p->x, 6); + test_int(p->y, 9); + + ecs_fini(world); +} + +void SystemCascade_custom_relation_add_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, AddParent, EcsOnUpdate, Position, ?Position(cascade(Rel))); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + + ecs_add_pair(world, e3, Rel, parent); /* depth 1 */ + ecs_add_pair(world, e4, Rel, parent); /* depth 1 */ + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + /* Before adding Position to parent, it wasn't being considered for the + * CASCADE column, so tables could have been ordered randomly. Make sure + * that queries can handle changes to depth after all tables are matched */ + ecs_set(world, parent, Position, {1, 2}); + + ctx = (Probe){0}; + + ecs_progress(world, 1); + + test_int(ctx.count, 5); + test_int(ctx.invoked, 3); + test_int(ctx.system, AddParent); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_assert(ctx.e[0] == e1 || ctx.e[1] == e1 || ctx.e[2] == e1); + test_assert(ctx.e[0] == e2 || ctx.e[1] == e2 || ctx.e[2] == e2); + test_assert(ctx.e[0] == parent || ctx.e[1] == parent || ctx.e[2] == parent); + test_assert(ctx.e[3] == e3 || ctx.e[4] == e3); + test_assert(ctx.e[3] == e4 || ctx.e[4] == e4); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + ecs_fini(world); +} + +void SystemCascade_custom_relation_adopt_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Rel); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, AddParent, EcsOnUpdate, Position, ?Position(cascade(Rel))); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_set(world, e1, Position, {1, 2}); + ecs_set(world, e2, Position, {1, 2}); + ecs_set(world, e3, Position, {1, 2}); + ecs_set(world, e4, Position, {1, 2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + ecs_add_pair(world, e3, Rel, parent); + ecs_add_pair(world, e4, Rel, parent); + + ctx = (Probe){0}; + + ecs_progress(world, 1); + + test_int(ctx.count, 5); + test_int(ctx.invoked, 3); + test_int(ctx.system, AddParent); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], parent); + test_int(ctx.e[3], e3); + test_int(ctx.e[4], e4); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + ecs_fini(world); +} + +void SystemCascade_custom_relation_rematch_w_empty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TAG(world, Rel); + + ecs_query_t *q = ecs_query_new(world, "?Position(cascade(Rel)), Velocity"); + test_assert(q != NULL); + + ecs_entity_t parent = ecs_new(world, Position); + ecs_add(world, parent, Velocity); + + ecs_entity_t child = ecs_new(world, Velocity); + ecs_add_pair(world, child, Rel, parent); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], parent); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], child); + test_assert(!ecs_query_next(&it)); + + // Change parent, trigger rematch + ecs_remove(world, parent, Position); + + it = ecs_query_iter(q); + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], parent); + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_int(it.entities[0], child); + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemManual.c b/fggl/ecs2/flecs/test/api/src/SystemManual.c new file mode 100644 index 0000000000000000000000000000000000000000..d440ca72d94e2627fb776692c4055e812a9470ef --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemManual.c @@ -0,0 +1,222 @@ +#include <api.h> + +void SystemManual_setup() { + ecs_tracing_enable(-3); +} + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + Velocity *v = NULL; + Mass *m = NULL; + + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + } + + if (it->column_count >= 3) { + m = ecs_term(it, Mass, 3); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + + if (v) { + v[i].x = 30; + v[i].y = 40; + } + + if (m) { + m[i] = 50; + } + } +} + +void SystemManual_1_type_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Iter, 0, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_run(world, Iter, 1, NULL); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +static int normal_count; + +static +void NormalSystem(ecs_iter_t *it) { + normal_count ++; +} + +static bool system_status_action_invoked = false; +static ecs_system_status_t enable_status = EcsSystemStatusNone; +static ecs_system_status_t active_status = EcsSystemStatusNone; + +static +void status_action( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_t status, + void *ctx) +{ + system_status_action_invoked = true; + + if (status == EcsSystemEnabled || status == EcsSystemDisabled) { + enable_status = status; + } else if (status == EcsSystemActivated || status == EcsSystemDeactivated) { + active_status = status; + } +} + +static +void reset_status() { + system_status_action_invoked = false; + enable_status = EcsSystemStatusNone; + active_status = EcsSystemStatusNone; +} + +void SystemManual_activate_status() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t normal_system = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "NormalSystem" + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = NormalSystem, + .status_callback = status_action + }); + + test_bool(system_status_action_invoked, true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + ecs_run(world, normal_system, 0, NULL); + test_int(normal_count, 0); + + reset_status(); + ecs_new(world, Position); + + test_bool(system_status_action_invoked, true); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemActivated); + + ecs_run(world, normal_system, 0, NULL); + test_int(normal_count, 1); + + reset_status(); + ecs_fini(world); + + test_bool(system_status_action_invoked, true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemDeactivated); +} + +static +void AddVelocity(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +void SystemManual_no_automerge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddVelocity, 0, Position, :Velocity); + + ECS_ENTITY(world, e1, Position); + + ecs_set_automerge(world, false); + + ecs_staging_begin(world); + ecs_world_t *stage = ecs_get_stage(world, 0); + + ecs_run(stage, AddVelocity, 1, NULL); + + test_assert(!ecs_has(stage, e1, Velocity)); + + ecs_staging_end(world); + + test_assert(!ecs_has(world, e1, Velocity)); + + ecs_merge(world); + + test_assert(ecs_has(world, e1, Velocity)); + + ecs_fini(world); +} + +static int dummy_ran = 0; + +void DummySystem(ecs_iter_t *it) { + ecs_entity_t Tag = ecs_term_id(it, 1); + ecs_add_id(it->world, Tag, Tag); + dummy_ran ++; +} + +void SystemManual_dont_run_w_unmatching_entity_query() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, DummySystem, 0, !$Tag); + + ecs_run(world, DummySystem, 0, NULL); + test_int(dummy_ran, 1); + + dummy_ran = 0; + + ecs_run(world, DummySystem, 0, NULL); + test_int(dummy_ran, 0); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemMisc.c b/fggl/ecs2/flecs/test/api/src/SystemMisc.c new file mode 100644 index 0000000000000000000000000000000000000000..50352d2ba9ba60ad9a63eee411cd17934c327a66 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemMisc.c @@ -0,0 +1,2101 @@ +#include <api.h> + +void SystemMisc_setup() { + // ecs_tracing_enable(-3); +} + +static +bool dummy_invoked = false; + +static +void Dummy(ecs_iter_t *it) { + dummy_invoked = true; + probe_system(it); +} + +void SystemMisc_invalid_not_without_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, !); + + ecs_fini(world); +} + +void SystemMisc_invalid_optional_without_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, ?); + + ecs_fini(world); +} + +void SystemMisc_invalid_system_without_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, SYSTEM:); + + ecs_fini(world); +} + +void SystemMisc_invalid_container_without_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, PARENT:); + + ecs_fini(world); +} + +void SystemMisc_invalid_cascade_without_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, CASCADE:); + + ecs_fini(world); +} + +void SystemMisc_invalid_entity_without_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, FOO.); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_without_id() { + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, .); + + test_assert(true); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_element() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate,, Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_element_w_space() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, , Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_or() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, || Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_or_w_space() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate,|| Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_or_w_not() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position || !Velocity); + + ecs_fini(world); +} + +void SystemMisc_invalid_not_w_or() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, !Position || Velocity); + + ecs_fini(world); +} + +void SystemMisc_invalid_0_w_and() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, 0, Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_0_w_from_system() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, SYSTEM:0); + + ecs_fini(world); +} + +void SystemMisc_invalid_0_w_from_container() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, PARENT:0); + + ecs_fini(world); +} + +void SystemMisc_invalid_0_w_from_cascade() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, CASCASDE.0); + + ecs_fini(world); +} + +void SystemMisc_invalid_0_w_from_entity() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Foo.0); + + ecs_fini(world); +} + +void SystemMisc_invalid_0_w_from_empty() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, :0); + + ecs_fini(world); +} + +void SystemMisc_invalid_or_w_empty() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, :Position || :Velocity); + + ecs_fini(world); +} + +void SystemMisc_invalid_component_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_entity_id() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Foo:Position); + + ecs_fini(world); +} + +void SystemMisc_invalid_null_string() { + ecs_world_t *world = ecs_init(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "Dummy", .add = {EcsOnUpdate} }, + .callback = Dummy + }); + + ecs_progress(world, 0); + + test_assert(dummy_invoked == true); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_string() { + ecs_world_t *world = ecs_init(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "Dummy", .add = {EcsOnUpdate} }, + .query.filter.expr = "", + .callback = Dummy + }); + + ecs_progress(world, 0); + + test_assert(dummy_invoked == true); + + ecs_fini(world); +} + +void SystemMisc_invalid_empty_string_w_space() { + ecs_world_t *world = ecs_init(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "Dummy", .add = {EcsOnUpdate} }, + .query.filter.expr = " ", + .callback = Dummy + }); + + ecs_progress(world, 0); + + test_assert(dummy_invoked == true); + + ecs_fini(world); +} + +void SystemMisc_invalid_mixed_src_modifier() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "Dummy", .add = {EcsOnUpdate} }, + .query.filter.expr = "SHARED:Position || Velocity", + .callback = Dummy + }); + + ecs_fini(world); +} + +void SystemMisc_redefine_row_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s; + { + ECS_TRIGGER(world, Dummy, EcsOnAdd, Position); + s = Dummy; + } + + ECS_TRIGGER(world, Dummy, EcsOnAdd, Position); + + test_assert(s == Dummy); + + ecs_fini(world); +} + +static int is_invoked; + +static +void IsInvoked(ecs_iter_t *it) { + is_invoked ++; +} + +void SystemMisc_system_w_or_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + + ECS_SYSTEM(world, IsInvoked, EcsOnUpdate, Position, flecs.core.Prefab || Disabled); + + test_int(is_invoked, 0); + + ecs_progress(world, 1); + + test_int(is_invoked, 1); + + ecs_fini(world); +} + +void SystemMisc_system_w_or_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, Entity, Position, Disabled); + + ECS_SYSTEM(world, IsInvoked, EcsOnUpdate, Position, Prefab || Disabled); + + test_int(is_invoked, 0); + + ecs_progress(world, 1); + + test_int(is_invoked, 1); + + ecs_fini(world); +} + +void SystemMisc_system_w_or_disabled_and_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_PREFAB(world, Prefab, Position); + ECS_ENTITY(world, Entity, Position, Disabled); + + ECS_SYSTEM(world, IsInvoked, EcsOnUpdate, Position, flecs.core.Prefab || Disabled); + + test_int(is_invoked, 0); + + ecs_progress(world, 1); + + test_int(is_invoked, 2); + is_invoked = false; + + ecs_fini(world); +} + +static +void TableColumns(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + ecs_type_t type = ecs_iter_type(it); + test_int(2, ecs_vector_count(type)); + + ecs_entity_t *components = ecs_vector_first(type, ecs_entity_t); + test_int(components[0], ecs_id(Position)); + test_int(components[1], ecs_id(Velocity)); + + void *column_0 = ecs_table_column(it, 0); + test_assert(column_0 == p); + + void *column_1 = ecs_table_column(it, 1); + test_assert(column_1 == v); + + is_invoked ++; +} + +void SystemMisc_table_columns_access() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + ECS_SYSTEM(world, TableColumns, EcsOnUpdate, Position, Velocity); + + test_int(is_invoked, 0); + + ecs_progress(world, 1); + + test_int(is_invoked, 1); + is_invoked = false; + + ecs_fini(world); +} + +static bool system_status_action_invoked = false; +static ecs_system_status_t enable_status = EcsSystemStatusNone; +static ecs_system_status_t active_status = EcsSystemStatusNone; + +static +void status_action( + ecs_world_t *world, + ecs_entity_t system, + ecs_system_status_t status, + void *ctx) +{ + system_status_action_invoked = true; + + if (status == EcsSystemEnabled || status == EcsSystemDisabled) { + enable_status = status; + } else if (status == EcsSystemActivated || status == EcsSystemDeactivated) { + active_status = status; + } +} + +static +void reset_status() { + system_status_action_invoked = false; + enable_status = EcsSystemStatusNone; + active_status = EcsSystemStatusNone; +} + +void SystemMisc_status_enable_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t dummy = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "Dummy", + .add = {EcsOnUpdate} + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = Dummy, + .status_callback = status_action + }); + + /* Setting the status action should have triggered the enabled status since + * the system is already enabled. There are no entities, so the system + * should not be active */ + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Enable enabled system. Should not trigger status. */ + reset_status(); + ecs_enable(world, dummy, true); + test_assert(system_status_action_invoked == false); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemStatusNone); + + /* After cleaning up the world, the Disabled status should have been + * triggered. There were no entities, so the Deactivated status shouldn't */ + reset_status(); + ecs_fini(world); + + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemStatusNone); +} + +void SystemMisc_status_enable_after_disable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t dummy = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "Dummy", + .add = {EcsOnUpdate} + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = Dummy, + .status_callback = status_action + }); + + /* Setting the status action should have triggered the enabled status since + * the system is already enabled. There are no entities, so the system + * should not be active */ + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Disable system, should trigger status */ + reset_status(); + ecs_enable(world, dummy, false); + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Enable enabled system. Should trigger status. */ + reset_status(); + ecs_enable(world, dummy, true); + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + /* After cleaning up the world, the Disabled status should have been + * triggered. There were no entities, so the Deactivated status shouldn't */ + reset_status(); + ecs_fini(world); + + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemStatusNone); +} + +void SystemMisc_status_disable_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t dummy = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "Dummy", + .add = {EcsOnUpdate} + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = Dummy, + .status_callback = status_action + }); + + /* Setting the status action should have triggered the enabled status since + * the system is already enabled. There are no entities, so the system + * should not be active */ + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Disable system, should trigger status action */ + reset_status(); + ecs_enable(world, dummy, false); + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Since the system is already disabled, no status should be triggered */ + reset_status(); + ecs_fini(world); + + test_assert(system_status_action_invoked == false); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemStatusNone); +} + +void SystemMisc_status_disable_after_disable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t dummy = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "Dummy", + .add = {EcsOnUpdate} + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = Dummy, + .status_callback = status_action + }); + + /* Setting the status action should have triggered the enabled status since + * the system is already enabled. There are no entities, so the system + * should not be active */ + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Disable system, should trigger status action */ + reset_status(); + ecs_enable(world, dummy, false); + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Disable system again, should not trigger status action */ + reset_status(); + ecs_enable(world, dummy, false); + test_assert(system_status_action_invoked == false); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemStatusNone); + + /* Since the system is already disabled, no status should be triggered */ + ecs_fini(world); + + test_assert(system_status_action_invoked == false); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemStatusNone); +} + +void SystemMisc_status_activate_after_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "Dummy", + .add = {EcsOnUpdate} + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = Dummy, + .status_callback = status_action + }); + + /* Setting the status action should have triggered the enabled status since + * the system is already enabled. There are no entities, so the system + * should not be active */ + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemStatusNone); + + /* Add entity with Position. Should activate system. */ + reset_status(); + ecs_new(world, Position); + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemActivated); + + /* After cleaning up the world, the Disabled and Deactivated status should + * have been triggered. */ + reset_status(); + ecs_fini(world); + + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemDeactivated); +} + +void SystemMisc_status_deactivate_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { + .name = "Dummy", + .add = {EcsOnUpdate} + }, + .query.filter.terms = {{ecs_id(Position)}}, + .callback = Dummy, + .status_callback = status_action + }); + + ecs_entity_t e = ecs_new(world, Position); + + /* Setting the status action should have triggered the enabled status since + * the system is already enabled. The system should be active since it has + * been matched with an entity. */ + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemEnabled); + test_assert(active_status == EcsSystemActivated); + + /* Delete the entity. Should trigger the Deactivated status */ + reset_status(); + ecs_delete(world, e); + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemStatusNone); + test_assert(active_status == EcsSystemDeactivated); + + /* After cleaning up the world, the Disabled status should have been + * triggered. */ + reset_status(); + ecs_fini(world); + + test_assert(system_status_action_invoked == true); + test_assert(enable_status == EcsSystemDisabled); + test_assert(active_status == EcsSystemStatusNone); +} + +void SystemMisc_dont_enable_after_rematch() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, ANY:Position, ANY:Velocity); + + /* Create an entity that is watched. Whenever components are added/removed + * to and/or from watched entities, a rematch is triggered. */ + ECS_PREFAB(world, Prefab, Position); + + ECS_ENTITY(world, Entity, (IsA, Prefab)); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* System is enabled but doesn't match with any entities */ + + test_bool(ecs_has_entity(world, Dummy, EcsDisabled), false); + ecs_progress(world, 1); + test_int(ctx.count, 0); + + /* Explicitly disable system before triggering a rematch */ + ecs_enable(world, Dummy, false); + test_bool(ecs_has_entity(world, Dummy, EcsDisabled), true); + + /* Trigger a rematch. System should still be disabled after this */ + ecs_add(world, Prefab, Velocity); + test_bool(ecs_has_entity(world, Dummy, EcsDisabled), true); + + ecs_progress(world, 1); + test_int(ctx.count, 0); + + /* Enable system. It is matched, so should now be invoked */ + ecs_enable(world, Dummy, true); + test_bool(ecs_has_entity(world, Dummy, EcsDisabled), false); + + ecs_progress(world, 1); + test_int(ctx.count, 1); + + ecs_fini(world); +} + +static void SysA(ecs_iter_t *it) +{ + ECS_COLUMN_COMPONENT(it, Velocity, 2); + ecs_add(it->world, it->entities[0], Velocity); +} + +static int b_invoked; +static ecs_entity_t b_entity; + +static void SysB(ecs_iter_t *it) +{ + b_invoked ++; + b_entity = it->entities[0]; +} + +void SystemMisc_ensure_single_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, SysA, EcsOnLoad, Position, !Velocity); + ECS_SYSTEM(world, SysB, EcsPostLoad, Velocity); + + ECS_ENTITY(world, MyEntity, Position); + + ecs_progress(world, 0); + + test_assert(b_invoked == 1); + + ecs_fini(world); +} + +static int test_table_count_invoked; + +static void TestTableCount(ecs_iter_t *it) { + test_int(it->table_count, 2); + test_int(it->inactive_table_count, 1); + test_table_count_invoked ++; +} + +void SystemMisc_table_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, TestTableCount, EcsOnUpdate, Position); + + // Create two active tables + ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + + // Create one inactive table + ecs_entity_t e3 = ecs_new(world, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + ecs_delete(world, e3); + + ecs_progress(world, 0); + + test_int(test_table_count_invoked, 2); + + ecs_fini(world); +} + +void SystemMisc_match_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, SysA, 0, SYSTEM:Position); + ECS_SYSTEM(world, SysB, 0, Position); + + ecs_run(world, SysB, 0, NULL); + + test_assert(b_invoked != 0); + test_assert(b_entity == SysA); + + ecs_fini(world); +} + +void SystemMisc_match_system_w_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, SysA, 0, SYSTEM:Position); + ECS_SYSTEM(world, SysB, 0, Position); + + ecs_run_w_filter(world, SysB, 0, 0, 0, &(ecs_filter_t){ + .include = ecs_type(Position) + }, NULL); + + test_assert(b_invoked != 0); + test_assert(b_entity == SysA); + + ecs_fini(world); +} + +void SystemMisc_system_initial_state() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, SysA, 0, Position); + + test_assert( ecs_has_entity(world, SysA, EcsInactive)); + test_assert( !ecs_has_entity(world, SysA, EcsDisabled)); + test_assert( !ecs_has_entity(world, SysA, EcsDisabledIntern)); + + ecs_fini(world); +} + +static +void FooSystem(ecs_iter_t *it) { } + +static +void BarSystem(ecs_iter_t *it) { } + +void SystemMisc_add_own_component() { + ecs_world_t * world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, FooSystem, 0, Position); + ECS_SYSTEM(world, BarSystem, 0, Position); + + ecs_set_ptr(world, BarSystem, Position, NULL ); + + /* Make sure code didn't assert */ + test_assert(true); + + ecs_fini(world); +} + +static bool action_a_invoked; +static bool action_b_invoked; + +static +void ActionA(ecs_iter_t *it) { + action_a_invoked = true; +} + +static +void ActionB(ecs_iter_t *it) { + action_b_invoked = true; +} + +void SystemMisc_change_system_action() { + ecs_world_t * world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t sys = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "Sys", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = ActionA + }); + + ecs_new(world, Position); + + test_bool(action_a_invoked, false); + test_bool(action_b_invoked, false); + + ecs_progress(world, 0); + + test_bool(action_a_invoked, true); + test_bool(action_b_invoked, false); + + action_a_invoked = false; + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {sys}, + .callback = ActionB + }); + + ecs_progress(world, 0); + + test_bool(action_a_invoked, false); + test_bool(action_b_invoked, true); + + ecs_fini(world); +} + +void SystemMisc_system_readeactivate() { + ecs_world_t * world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + /* No entities, system should be deactivated */ + test_assert( ecs_has_entity(world, Dummy, EcsInactive)); + + ecs_entity_t e = ecs_new(world, Position); + + /* System should be active, one entity is matched */ + test_assert( !ecs_has_entity(world, Dummy, EcsInactive)); + + ecs_delete(world, e); + + /* System is not automatically deactivated */ + test_assert( !ecs_has_entity(world, Dummy, EcsInactive)); + + /* Manually deactivate system that aren't matched with entities */ + ecs_deactivate_systems(world); + + /* System should be deactivated */ + test_assert( ecs_has_entity(world, Dummy, EcsInactive)); + + ecs_fini(world); +} + +static +void Dummy1(ecs_iter_t *it) { } + +static +void Dummy2(ecs_iter_t *it) { } + +void SystemMisc_system_readeactivate_w_2_systems() { + ecs_world_t * world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, Dummy1, EcsOnUpdate, Position); + ECS_SYSTEM(world, Dummy2, EcsOnUpdate, Mass); + + /* No entities, system should be deactivated */ + test_assert( ecs_has_entity(world, Dummy1, EcsInactive)); + test_assert( ecs_has_entity(world, Dummy2, EcsInactive)); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_new(world, Mass); + + /* Systems should be active, one entity is matched */ + test_assert( !ecs_has_entity(world, Dummy1, EcsInactive)); + test_assert( !ecs_has_entity(world, Dummy2, EcsInactive)); + + ecs_delete(world, e1); + + /* System is not automatically deactivated */ + test_assert( !ecs_has_entity(world, Dummy1, EcsInactive)); + test_assert( !ecs_has_entity(world, Dummy2, EcsInactive)); + + /* Manually deactivate system that aren't matched with entities */ + ecs_deactivate_systems(world); + + /* System should be deactivated */ + test_assert( ecs_has_entity(world, Dummy1, EcsInactive)); + test_assert( !ecs_has_entity(world, Dummy2, EcsInactive)); + + ecs_fini(world); +} + +void SystemMisc_add_to_system_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_defer_begin(world); + + ecs_add(world, Dummy, Tag); + + ecs_defer_end(world); + + ecs_progress(world, 0); + test_assert(dummy_invoked == true); + + ecs_fini(world); +} + +static +void Foo(ecs_iter_t *it) { } + +void SystemMisc_add_to_lazy_system_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + ecs_new(world, Position); + + ecs_defer_begin(world); + + ecs_add(world, Dummy, Tag); + + ecs_defer_end(world); + + ECS_SYSTEM(world, Foo, EcsOnUpdate, [in] Position); + + ecs_progress(world, 0); + test_assert(dummy_invoked == true); + + ecs_fini(world); +} + +static +void Action(ecs_iter_t *it) { } + +void SystemMisc_redefine_null_signature() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t s_1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "System", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Action + }); + + ecs_entity_t s_2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "System", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Action + }); + + test_assert(s_1 == s_2); + + ecs_fini(world); +} + +void SystemMisc_redefine_0_signature() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t s_1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "System", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Action + }); + + ecs_entity_t s_2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = { .name = "System", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Action + }); + + test_assert(s_1 == s_2); + + ecs_fini(world); +} + +void SystemMisc_one_named_column_of_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){{ + { ecs_id(Position), .name = "pos" }, + { ecs_id(Velocity) } + }})); + + test_int(f.term_count, 2); + test_int(f.term_count_actual, 2); + + ecs_term_t * + term = &f.terms[0]; + test_assert(term->oper == EcsAnd); + test_assert(term->args[0].set.mask == EcsDefaultSet); + test_assert(term->args[0].entity == EcsThis); + test_assert(term->inout == EcsInOutDefault); + test_assert(term->id == ecs_id(Position)); + test_str(term->name, "pos"); + + term = &f.terms[1]; + test_assert(term->oper == EcsAnd); + test_assert(term->args[0].set.mask == EcsDefaultSet); + test_assert(term->args[0].entity == EcsThis); + test_assert(term->inout == EcsInOutDefault); + test_assert(term->id == ecs_id(Velocity)); + test_str(term->name, NULL); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void SystemMisc_two_named_columns_of_two() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_filter_t f; + test_int(0, ecs_filter_init(world, &f, &(ecs_filter_desc_t){{ + { ecs_id(Position), .name = "pos" }, + { ecs_id(Velocity), .name = "vel" } + }})); + + test_int(f.term_count, 2); + test_int(f.term_count_actual, 2); + + ecs_term_t * + term = &f.terms[0]; + test_assert(term->oper == EcsAnd); + test_assert(term->args[0].set.mask == EcsDefaultSet); + test_assert(term->args[0].entity == EcsThis); + test_assert(term->inout == EcsInOutDefault); + test_assert(term->id == ecs_id(Position)); + test_str(term->name, "pos"); + + term = &f.terms[1]; + test_assert(term->oper == EcsAnd); + test_assert(term->args[0].set.mask == EcsDefaultSet); + test_assert(term->args[0].entity == EcsThis); + test_assert(term->inout == EcsInOutDefault); + test_assert(term->id == ecs_id(Velocity)); + test_str(term->name, "vel"); + + ecs_filter_fini(&f); + + ecs_fini(world); +} + +void SystemMisc_get_column_by_name() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position p, Velocity v"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + + while (ecs_query_next(&it)) { + int32_t p_i = ecs_column_index_from_name(&it, "p"); + test_assert(p_i == 1); + int32_t v_i = ecs_column_index_from_name(&it, "v"); + test_assert(v_i == 2); + } + + ecs_fini(world); +} + +void SystemMisc_get_column_by_name_not_found() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position p, Velocity v"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + + while (ecs_query_next(&it)) { + int32_t p_i = ecs_column_index_from_name(&it, "r"); + test_assert(p_i == 0); + } + + ecs_fini(world); +} + +void SystemMisc_get_column_by_name_no_names() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t e = ecs_new(world, 0); + ecs_add(world, e, Position); + ecs_add(world, e, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, Velocity"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(q); + + while (ecs_query_next(&it)) { + int32_t p_i = ecs_column_index_from_name(&it, "p"); + test_assert(p_i == 0); + int32_t v_i = ecs_column_index_from_name(&it, "v"); + test_assert(v_i == 0); + } + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_same_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + + test_assert(s1 == s2); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_null_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); + + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); + + test_assert(s1 == s2); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_0_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + + test_assert(s1 == s2); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_different_expr() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position", + .callback = Dummy + }); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_null_and_expr() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); +} + +void SystemMisc_redeclare_system_expr_and_null() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); +} + +void SystemMisc_redeclare_system_expr_and_0() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); +} + +void SystemMisc_redeclare_system_0_and_expr() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); +} + +void SystemMisc_redeclare_system_0_and_null() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + + test_expect_abort(); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); +} + +void SystemMisc_redeclare_system_null_and_0() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + + test_assert(s1 == s2); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_explicit_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = s1, .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + + test_assert(s1 == s2); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_explicit_id_null_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); + + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = s1, .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = NULL, + .callback = Dummy + }); + + test_assert(s1 == s2); + + ecs_fini(world); +} + +void SystemMisc_redeclare_system_explicit_id_no_name() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_entity_t s1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + + ecs_entity_t s2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = s1, .name = NULL, .add = {EcsOnUpdate} }, + .query.filter.expr = "Position, Velocity", + .callback = Dummy + }); + + test_assert(s1 == s2); + test_str(ecs_get_name(world, s1), "Move"); + + ecs_fini(world); +} + +void SystemMisc_declare_different_id_same_name() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + + ecs_entity_t s_1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = e1, .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + test_assert(e1 == s_1); + + ecs_entity_t s_2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = e2, .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + test_assert(e2 == s_2); + + test_assert(e1 != e2); + + ecs_fini(world); +} + +void SystemMisc_declare_different_id_same_name_w_scope() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t scope = ecs_new(world, 0); + ecs_set_scope(world, scope); + + ecs_entity_t e1 = ecs_new(world, 0); + ecs_entity_t e2 = ecs_new(world, 0); + + ecs_entity_t s_1 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = e1, .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + test_assert(e1 == s_1); + + ecs_entity_t s_2 = ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {.entity = e2, .name = "Move", .add = {EcsOnUpdate} }, + .query.filter.expr = "0", + .callback = Dummy + }); + test_assert(e2 == s_2); + + test_assert(e1 != e2); + + ecs_fini(world); +} + +void SystemMisc_rw_in_implicit_any() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, ANY:Velocity"); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == true); + + ecs_fini(world); +} + +void SystemMisc_rw_in_implicit_shared() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, SHARED:Velocity"); + + ecs_entity_t base = ecs_new(world, Velocity); + ecs_entity_t e = ecs_new(world, Position); + ecs_add_pair(world, e, EcsIsA, base); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == true); + + ecs_fini(world); +} + +void SystemMisc_rw_in_implicit_from_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, :Velocity"); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == true); + + ecs_fini(world); +} + +void SystemMisc_rw_in_implicit_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, f, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, f:Velocity"); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == true); + + ecs_fini(world); +} + +void SystemMisc_rw_out_explicit_any() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, [out] ANY:Velocity"); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == false); + + ecs_fini(world); +} + +void SystemMisc_rw_out_explicit_shared() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, [out] SHARED:Velocity"); + + ecs_entity_t base = ecs_new(world, Velocity); + ecs_entity_t e = ecs_new(world, Position); + ecs_add_pair(world, e, EcsIsA, base); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == false); + + ecs_fini(world); +} + +void SystemMisc_rw_out_explicit_from_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, [out] :Velocity"); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == false); + + ecs_fini(world); +} + +void SystemMisc_rw_out_explicit_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, f, Velocity); + + ecs_query_t *q = ecs_query_new(world, "Position, [out] f:Velocity"); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + + ecs_iter_t it = ecs_query_iter(q); + test_assert(ecs_query_next(&it) == true); + test_assert(ecs_is_readonly(&it, 1) == false); + test_assert(ecs_is_readonly(&it, 2) == false); + + ecs_fini(world); +} + +void SystemMisc_activate_system_for_table_w_n_pairs() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pair); + ECS_SYSTEM(world, Dummy, EcsOnUpdate, (Pair, *)); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TYPE(world, Type, (Pair, TagA), (Pair, TagB)); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + ecs_progress(world, 0); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + + test_int(ctx.e[0], e); + test_int(ctx.e[1], e); + + test_int(ctx.c[0][0], ecs_pair(Pair, TagA)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[1][0], ecs_pair(Pair, TagB)); + test_int(ctx.s[1][0], 0); + + ecs_fini(world); +} + +void SystemMisc_get_query() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Position); + + ecs_set(world, 0, Position, {0, 0}); + ecs_set(world, 0, Position, {1, 0}); + ecs_set(world, 0, Position, {2, 0}); + + ecs_query_t *q = ecs_get_system_query(world, Dummy); + test_assert(q != NULL); + + int32_t count = 0; + + ecs_iter_t it = ecs_query_iter(q); + while (ecs_query_next(&it)) { + Position *p = ecs_term(&it, Position, 1); + test_assert(p != NULL); + + int i; + for (i = 0; i < it.count; i ++) { + test_int(p[i].x, i); + count ++; + } + } + + test_int(count, 3); + + ecs_fini(world); +} + +void SystemMisc_set_get_context() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int32_t ctx_a, ctx_b; + + ecs_entity_t s = ecs_system_init(world, &(ecs_system_desc_t){ + .entity.name = "MySystem", + .query.filter.terms = {{Tag}}, + .callback = Dummy, + .ctx = &ctx_a + }); + test_assert(s != 0); + + test_assert(ecs_get_system_ctx(world, s) == &ctx_a); + + test_assert(ecs_system_init(world, &(ecs_system_desc_t){ + .entity.entity = s, + .ctx = &ctx_b + }) == s); + + test_assert(ecs_get_system_ctx(world, s) == &ctx_b); + + ecs_fini(world); +} + +void SystemMisc_set_get_binding_context() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int32_t ctx_a, ctx_b; + + ecs_entity_t s = ecs_system_init(world, &(ecs_system_desc_t){ + .entity.name = "MySystem", + .query.filter.terms = {{Tag}}, + .callback = Dummy, + .binding_ctx = &ctx_a + }); + test_assert(s != 0); + + test_assert(ecs_get_system_binding_ctx(world, s) == &ctx_a); + + test_assert(ecs_system_init(world, &(ecs_system_desc_t){ + .entity.entity = s, + .binding_ctx = &ctx_b + }) == s); + + test_assert(ecs_get_system_binding_ctx(world, s) == &ctx_b); + + ecs_fini(world); +} + +void SystemMisc_deactivate_after_disable() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, Tag); + + ecs_entity_t e = ecs_new_w_id(world, Tag); + test_assert(!ecs_has_id(world, Dummy, EcsInactive)); + + ecs_enable(world, Dummy, false); + test_assert(!ecs_has_id(world, Dummy, EcsInactive)); + test_assert(ecs_has_id(world, Dummy, EcsDisabled)); + + ecs_delete(world, e); + + test_assert(!ecs_has_id(world, Dummy, EcsInactive)); + test_assert(ecs_has_id(world, Dummy, EcsDisabled)); + + ecs_fini(world); +} + +void SystemMisc_system_w_self() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t self = ecs_new_id(world); + + Probe ctx = {0}; + ecs_entity_t system = ecs_system_init(world, &(ecs_system_desc_t){ + .query.filter.terms = {{.id = Tag}}, + .callback = Dummy, + .self = self + }); + test_assert(system != 0); + + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + + ecs_run(world, system, 0, NULL); + + test_int(ctx.count, 1); + test_assert(ctx.system == system); + test_assert(ctx.self == self); + + ecs_fini(world); +} + +void SystemMisc_delete_system() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + Probe ctx = {0}; + ecs_entity_t system = ecs_system_init(world, &(ecs_system_desc_t) { + .query.filter.terms = {{.id = Tag}}, + .callback = Dummy + }); + test_assert(system != 0); + + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + + ecs_run(world, system, 0, NULL); + test_int(ctx.count, 1); + test_assert(ctx.system == system); + + ecs_delete(world, system); + test_assert(!ecs_is_alive(world, system)); + + ecs_fini(world); +} + +void SystemMisc_delete_pipeline_system() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + Probe ctx = {0}; + + // Create system before + test_assert(ecs_system_init(world, &(ecs_system_desc_t) { + .entity.add = {EcsOnUpdate}, + .query.filter.terms = {{.id = Tag}}, + .callback = Dummy + }) != 0); + + ecs_entity_t system = ecs_system_init(world, &(ecs_system_desc_t) { + .entity.add = {EcsOnUpdate}, + .query.filter.terms = {{.id = Tag}}, + .callback = Dummy + }); + test_assert(system != 0); + + // Create system after + test_assert(ecs_system_init(world, &(ecs_system_desc_t) { + .entity.add = {EcsOnUpdate}, + .query.filter.terms = {{.id = Tag}}, + .callback = Dummy + }) != 0); + + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + + ecs_progress(world, 0); + test_int(ctx.count, 3); + + ctx.count = 0; + + ecs_delete(world, system); + + ecs_progress(world, 0); + test_int(ctx.count, 2); + + ecs_fini(world); +} + +static int ctx_value; +static +void ctx_free(void *ctx) { + test_assert(&ctx_value == ctx); + ctx_value ++; +} + +static int binding_ctx_value; +static +void binding_ctx_free(void *ctx) { + test_assert(&binding_ctx_value == ctx); + binding_ctx_value ++; +} + +void SystemMisc_delete_system_w_ctx() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + Probe ctx = {0}; + ecs_entity_t system = ecs_system_init(world, &(ecs_system_desc_t) { + .query.filter.terms = {{.id = Tag}}, + .callback = Dummy, + .ctx = &ctx_value, + .ctx_free = ctx_free, + .binding_ctx = &binding_ctx_value, + .binding_ctx_free = binding_ctx_free + }); + test_assert(system != 0); + + test_assert(ecs_get_system_ctx(world, system) == &ctx_value); + test_assert(ecs_get_system_binding_ctx(world, system) + == &binding_ctx_value); + + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + + ecs_run(world, system, 0, NULL); + test_int(ctx.count, 1); + test_assert(ctx.system == system); + + ecs_delete(world, system); + test_assert(!ecs_is_alive(world, system)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemOnDemand.c b/fggl/ecs2/flecs/test/api/src/SystemOnDemand.c new file mode 100644 index 0000000000000000000000000000000000000000..6b58197c3423e5333bb837572dc12f6c5ab5c1a6 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemOnDemand.c @@ -0,0 +1,858 @@ +#include <api.h> + +static bool invoked = false; +static bool invoked2 = false; + +static +void OutSystem(ecs_iter_t *it) { + invoked = true; +} + +static +void OutSystem2(ecs_iter_t *it) { + invoked2 = true; +} + +static +void InSystem(ecs_iter_t *it) { +} + +static +void InSystem2(ecs_iter_t *it) { +} + +bool is_enabled( + ecs_world_t *world, + ecs_entity_t system) +{ + return !ecs_has_entity(world, system, EcsDisabled) && + !ecs_has_entity(world, system, EcsDisabledIntern); +} + +void SystemOnDemand_enable_out_after_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_in_after_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Position); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_out_after_in_2_out_1_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, [out] Velocity, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Type); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_out_after_in_1_out_2_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, [in] Velocity); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time. Make sure + * entity matches InSystem as well as otherwise the system won't be active + * and the OutSystem won't be enabled. */ + ecs_new(world, Type); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_in_after_out_2_out_1_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, [out] Velocity, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Type); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_in_after_out_1_out_2_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time. Make sure + * entity matches InSystem as well as otherwise the system won't be active + * and the OutSystem won't be enabled. */ + ecs_new(world, Type); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, [in] Velocity); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_disable_after_disable_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* Disabling the [in] system should disable the [out] system */ + ecs_enable(world, InSystem, false); + invoked = false; + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_disable_after_disable_in_2_out_1_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, [out] Velocity, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Type); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* Disabling the [in] system should disable the [out] system */ + ecs_enable(world, InSystem, false); + invoked = false; + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_disable_after_disable_in_1_out_2_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, [in] Velocity); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Create dummy entity so system won't be disabled all the time. Make sure + * entity matches InSystem as well as otherwise the system won't be active + * and the OutSystem won't be enabled. */ + ecs_new(world, Type); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* Disabling the [in] system should disable the [out] system */ + ecs_enable(world, InSystem, false); + invoked = false; + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_table_after_out() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + ecs_new(world, Position); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_table_after_out_and_in() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + /* The InSystem is not active yet since there are no matching entities for + * it, and therefore OutSystem won't be active either. */ + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_new(world, Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_table_after_out_and_in_overlapping_columns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, OutType, Position, Velocity); + ECS_TYPE(world, InType, Position, Mass); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, Velocity, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, Mass); + + /* The InSystem is not active yet since there are no matching entities for + * it, and therefore OutSystem won't be active either. */ + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + /* Create entity that will activate InSystem but not OutSystem. OutSystem + * should now be enabled, but it is not active yet. */ + ecs_new(world, InType); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == false); + + /* Create entity that matches the signature of OutType. The system should + * now be enabled and active. */ + ecs_new(world, OutType); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_1_out_system_2_in_systems() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + /* Create entity that will activate InSystems and OutSystem */ + ecs_new(world, Position); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ECS_SYSTEM(world, InSystem2, EcsOnUpdate, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_enable(world, InSystem, false); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_enable(world, InSystem2, false); + + test_assert(is_enabled(world, OutSystem) == false); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_1_out_system_2_in_systems_different_columns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + /* Create entity that will activate InSystems and OutSystem */ + ecs_new(world, Type); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, [out] Velocity, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ECS_SYSTEM(world, InSystem2, EcsOnUpdate, [in] Velocity); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_enable(world, InSystem, false); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_enable(world, InSystem2, false); + + test_assert(is_enabled(world, OutSystem) == false); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_1_out_system_2_in_systems_overlapping_columns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + + /* Create entity that will activate InSystems and OutSystem */ + ecs_new(world, Type); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, [out] Velocity, [out] Mass, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, [in] Velocity); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ECS_SYSTEM(world, InSystem2, EcsOnUpdate, [in] Velocity, [in] Mass); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_enable(world, InSystem, false); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_enable(world, InSystem2, false); + + test_assert(is_enabled(world, OutSystem) == false); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_disable_after_inactive_in_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_new(world, Position); + + /* Create an entity that when deleted disables the InSystem */ + ecs_entity_t in_entity = ecs_new(world, Type); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, Velocity); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* InSystem should now deactivate, which should disable OutSystem */ + ecs_delete(world, in_entity); + + test_assert(is_enabled(world, OutSystem) == false); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_disable_after_2_inactive_in_systems() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_new(world, Position); + + /* Create an entity that when deleted disables the InSystem */ + ecs_entity_t in_entity = ecs_new(world, Type); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, Velocity); + ECS_SYSTEM(world, InSystem2, EcsOnUpdate, [in] Position, Velocity); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* InSystem should now deactivate, which should disable OutSystem */ + ecs_delete(world, in_entity); + + test_assert(is_enabled(world, OutSystem) == false); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_disable_after_2_inactive_in_systems_different_columns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type1, Position, Velocity); + ECS_TYPE(world, Type2, Position, Mass); + + ecs_new(world, Position); + + /* Create an entity that when deleted disables the InSystem */ + ecs_entity_t in_entity = ecs_new(world, Type1); + ecs_entity_t in2_entity = ecs_new(world, Type2); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, Velocity); + ECS_SYSTEM(world, InSystem2, EcsOnUpdate, [in] Position, Mass); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* InSystem should now deactivate, but OutSystem should still be enabled */ + ecs_delete(world, in_entity); + + test_assert(is_enabled(world, OutSystem) == true); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == true); + + /* InSystem2 should now deactivate, which should disable OutSystem */ + ecs_delete(world, in2_entity); + + test_assert(is_enabled(world, OutSystem) == false); + invoked = false; + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_enable_2_output_1_input_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + ECS_SYSTEM(world, OutSystem2, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + test_assert(is_enabled(world, OutSystem2) == false); + ecs_progress(world, 0); + test_assert(invoked2 == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + test_assert(is_enabled(world, OutSystem2) == true); + ecs_progress(world, 0); + test_assert(invoked2 == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_2_output_1_input_system_different_columns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_new(world, Type); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + ECS_SYSTEM(world, OutSystem2, EcsOnUpdate, [out] Velocity, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + test_assert(is_enabled(world, OutSystem2) == false); + ecs_progress(world, 0); + test_assert(invoked2 == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, [in] Velocity); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + test_assert(is_enabled(world, OutSystem2) == true); + ecs_progress(world, 0); + test_assert(invoked2 == true); + + ecs_fini(world); +} + +void SystemOnDemand_enable_2_output_1_input_system_overlapping_columns() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + + ecs_new(world, Type); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, [out] Velocity, SYSTEM:OnDemand); + ECS_SYSTEM(world, OutSystem2, EcsOnUpdate, [out] Position, [out] Mass, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + test_assert(is_enabled(world, OutSystem2) == false); + ecs_progress(world, 0); + test_assert(invoked2 == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position, [in] Velocity, [in] Mass); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + test_assert(is_enabled(world, OutSystem2) == true); + ecs_progress(world, 0); + test_assert(invoked2 == true); + + ecs_fini(world); +} + +void SystemOnDemand_out_not_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_new(world, Position); + + /* If a system has a NOT column that is also an [out] column, the system + * explicitly states that it will create the component. Therefore the system + * will always be enabled if there are enabled input systems, even if those + * input systems are not active (no matched entities). This way, OutSystem + * can become enabled even though there are no entities with Velocity yet */ + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, Position, [out] !Velocity, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, Position, [in] Velocity); + + /* There are no matching entities with the InSystem, but the OutSystem + * will be enabled nonetheless, as it explicitly stated it will create the + * Velocity component. */ + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_trigger_on_manual() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_new(world, Position); + + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, 0, [in] Position); + + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_trigger_on_manual_not_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_new(world, Position); + + /* If a system has a NOT column that is also an [out] column, the system + * explicitly states that it will create the component. Therefore the system + * will always be enabled if there are enabled input systems, even if those + * input systems are not active (no matched entities). This way, OutSystem + * can become enabled even though there are no entities with Velocity yet */ + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, Position, [out] !Velocity, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, 0, Position, [in] Velocity); + + /* There are no matching entities with the InSystem, but the OutSystem + * will be enabled nonetheless, as it explicitly stated it will create the + * Velocity component. */ + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} + +void SystemOnDemand_on_demand_task_w_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, MyEntity, Position); + + /* If a system has an out column that matches an entity, it should still + * behave like an OnDemand system, only enabling when there is interest for + * the component in the [out] column. */ + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] MyEntity:Position, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, 0, [in] Position); + + /* There are no matching entities with the InSystem, but the OutSystem + * will be enabled nonetheless, as it explicitly stated it will create the + * Velocity component. */ + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* When Position is removed from MyEntity, the system must be disabled */ + invoked = false; + ecs_remove(world, MyEntity, Position); + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_on_demand_task_w_not_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, MyEntity, 0); + + /* If a system has an out column that matches an entity, it should still + * behave like an OnDemand system, only enabling when there is interest for + * the component in the [out] column, even if that column has a NOT operator */ + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] !MyEntity:Position, SYSTEM:OnDemand); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, 0, [in] Position); + + /* There are no matching entities with the InSystem, but the OutSystem + * will be enabled nonetheless, as it explicitly stated it will create the + * Velocity component. */ + test_assert(is_enabled(world, OutSystem) == true); + ecs_progress(world, 0); + test_assert(invoked == true); + + /* When Position is added to MyEntity, the system should not be ran */ + invoked = false; + ecs_add(world, MyEntity, Position); + ecs_progress(world, 0); + test_assert(invoked == false); + + ecs_fini(world); +} + +void SystemOnDemand_enable_after_user_disable() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, OutSystem, EcsOnUpdate, [out] Position, SYSTEM:OnDemand); + + /* Explicitly disable system. System should not automatically enable once + * demand is created */ + ecs_enable(world, OutSystem, false); + + /* Create dummy entity so system won't be disabled all the time */ + ecs_new(world, Position); + + test_assert(is_enabled(world, OutSystem) == false); + ecs_progress(world, 0); + test_assert(invoked == false); + + ECS_SYSTEM(world, InSystem, EcsOnUpdate, [in] Position); + + /* System should still be disabled, since user explicitly disabled it */ + test_assert(is_enabled(world, OutSystem) == false); + + /* Explicitly enable system. This should enable the system, since now there + * is also demand */ + ecs_enable(world, OutSystem, true); + test_assert(is_enabled(world, OutSystem) == true); + + ecs_progress(world, 0); + test_assert(invoked == true); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemOnSet.c b/fggl/ecs2/flecs/test/api/src/SystemOnSet.c new file mode 100644 index 0000000000000000000000000000000000000000..499cfa2cd9bab7e72504f8b70435600ccdcd24f4 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemOnSet.c @@ -0,0 +1,1148 @@ +#include <api.h> + +static +void OnPosition(ecs_iter_t *it) { + probe_system(it); +} + +static +void Add_to_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + + test_assert( !!ecs_get_type(it->world, it->entities[i])); + } + + if (ctx->component_2) { + ecs_add_id(it->world, it->entities[i], ctx->component_2); + } + + ctx->entity_count ++; + } +} + +static +void Remove_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (ctx->component) { + ecs_remove_id(it->world, e, ctx->component); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, e, ctx->component_2); + } + + ctx->entity_count ++; + } +} + +static Probe pv_probe; + +static +void On_PV(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + probe_system_w_ctx(it, &pv_probe); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } +} + +void SystemOnSet_set_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ctx = (Probe){ 0 }; + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemOnSet_set_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ctx = (Probe){ 0 }; + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_set_1_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Mass); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ctx = (Probe){ 0 }; + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ctx = (Probe){ 0 }; + + ecs_set(world, e, Mass, {10}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ecs_fini(world); +} + +void SystemOnSet_bulk_new_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ecs_id(Position)}, + .count = 1 + }, + (void*[]){ + (Position[]) { + {10, 20}, + {30, 40}, + {50, 60} + } + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemOnSet_bulk_new_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position), + ecs_id(Velocity) + }, + .count = 2 + }, + (void*[]){ + (Position[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]) { + {10, 20}, + {30, 40}, + {50, 60} + } + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_bulk_new_2_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position), + ecs_id(Velocity) + }, + .count = 2 + }, + (void*[]){ + (Position[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]) { + {10, 20}, + {30, 40}, + {50, 60} + } + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemOnSet_bulk_new_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position), + ecs_id(Velocity), + ecs_id(Mass) + }, + .count = 3 + }, + (void*[]){ + (Position[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Mass[]) { + 10, + 20, + 30 + } + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ecs_fini(world); +} + +void SystemOnSet_bulk_new_3_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Velocity, Mass); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Position), + ecs_id(Velocity), + ecs_id(Mass) + }, + .count = 3 + }, + (void*[]){ + (Position[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Velocity[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Mass[]) { + 10, + 20, + 30 + } + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_bulk_new_1_from_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, Base, Position); + ECS_TYPE(world, Type, (IsA, Base), Velocity, Mass); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new_w_data(world, 3, + &(ecs_ids_t){ + .array = (ecs_entity_t[]){ + ecs_id(Velocity), + ecs_id(Mass), + ecs_pair(EcsIsA, Base) + }, + .count = 3 + }, + (void*[]){ + (Velocity[]) { + {10, 20}, + {30, 40}, + {50, 60} + }, + (Mass[]) { + 10, + 20, + 30 + } + }); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Base); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ecs_fini(world); +} + +void SystemOnSet_set_1_of_2_1_from_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Base); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_set_1_of_3_1_from_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_add(world, e, Mass); + test_int(ctx.invoked, 0); + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Base); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ctx = (Probe){ 0 }; + + ecs_set(world, e, Mass, {10}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Base); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ecs_fini(world); +} + +void SystemOnSet_add_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Velocity); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Base); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_add_base_to_1_overridden() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void SystemOnSet_add_base_to_2_overridden() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, Base, Position, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position, ANY:Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void SystemOnSet_add_base_to_1_of_2_overridden() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, Base, Position, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Velocity); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Base); + + ctx = (Probe){ 0 }; + + e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void SystemOnSet_on_set_after_remove_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemOnSet_no_set_after_remove_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsOnSet, ANY:Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, ecs_pair(EcsIsA, Base)); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void SystemOnSet_un_set_after_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, OnPosition, EcsUnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + + ecs_fini(world); +} + +void SystemOnSet_un_set_after_remove_base() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, Base, Position); + ECS_SYSTEM(world, OnPosition, EcsUnSet, ANY:Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.invoked, 0); + + ecs_add_pair(world, e, EcsIsA, Base); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, ecs_pair(EcsIsA, Base)); + test_int(ctx.invoked, 1); + + ecs_fini(world); +} + +void SystemOnSet_add_to_current_in_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, Add_to_current, EcsOnSet, Position); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {11, 21}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {12, 22}); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + const Position *p = ecs_get(world, e1, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_int(p->x, 11); + test_int(p->y, 21); + + p = ecs_get(world, e3, Position); + test_int(p->x, 12); + test_int(p->y, 22); + + ecs_fini(world); +} + +void SystemOnSet_remove_from_current_in_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_SYSTEM(world, Remove_from_current, EcsOnSet, Position); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, Type); + ecs_entity_t e2 = ecs_new(world, Type); + ecs_entity_t e3 = ecs_new(world, Type); + + e1 = ecs_set(world, e1, Position, {10, 20}); + e2 = ecs_set(world, e2, Position, {11, 21}); + e3 = ecs_set(world, e3, Position, {12, 22}); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( !ecs_has(world, e1, Velocity)); + test_assert( !ecs_has(world, e2, Velocity)); + test_assert( !ecs_has(world, e3, Velocity)); + + const Position *p = ecs_get(world, e1, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_int(p->x, 11); + test_int(p->y, 21); + + p = ecs_get(world, e3, Position); + test_int(p->x, 12); + test_int(p->y, 22); + + ecs_fini(world); +} + +void SystemOnSet_remove_set_component_in_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_SYSTEM(world, Remove_from_current, EcsOnSet, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + /* Create entities from scratch so they don't have the EcsName component */ + ecs_entity_t e1 = ecs_new(world, Type); + ecs_entity_t e2 = ecs_new(world, Type); + ecs_entity_t e3 = ecs_new(world, Type); + + e1 = ecs_set(world, e1, Position, {10, 20}); + e2 = ecs_set(world, e2, Position, {11, 21}); + e3 = ecs_set(world, e3, Position, {12, 22}); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + ecs_fini(world); +} + +void SystemOnSet_match_table_created_w_add_in_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, Add_to_current, EcsOnSet, Position); + ECS_SYSTEM(world, On_PV, EcsOnUpdate, Position, Velocity); + + IterData add_ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &add_ctx); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e2 = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e3 = ecs_set(world, 0, Position, {10, 20}); + + ecs_progress(world, 1); + + test_int(pv_probe.count, 3); + test_int(pv_probe.invoked, 1); + test_int(pv_probe.system, On_PV); + test_int(pv_probe.column_count, 2); + test_null(pv_probe.param); + + test_int(pv_probe.e[0], e1); + test_int(pv_probe.e[1], e2); + test_int(pv_probe.e[2], e3); + test_int(pv_probe.c[0][0], ecs_id(Position)); + test_int(pv_probe.s[0][0], 0); + test_int(pv_probe.c[0][1], ecs_id(Velocity)); + test_int(pv_probe.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_set_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, ?Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ctx = (Probe){ 0 }; + + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemOnSet_set_from_nothing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, OnPosition, EcsOnSet, Position, :Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnPosition); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ctx = (Probe){ 0 }; + ecs_set(world, e, Velocity, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +static +void AddNull(ecs_iter_t *it) { + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_type(it->world, it->entities[i], NULL); + } +} + +void SystemOnSet_add_null_type_in_on_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, AddNull, EcsOnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, AddNull); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + test_assert( ecs_has(world, e, Position)); + + ecs_fini(world); +} + +static +void Add0(ecs_iter_t *it) { + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_id(it->world, it->entities[i], 0); + } +} + +void SystemOnSet_add_0_entity_in_on_set() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, Add0, EcsOnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + test_expect_abort(); + ecs_set(world, 0, Position, {10, 20}); +} + +static int dummy_invoked = 0; + +static +void Dummy(ecs_iter_t *it) { + dummy_invoked ++; +} + +void SystemOnSet_on_set_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, Dummy, EcsOnSet, Position); + + ECS_PREFAB(world, Prefab, 0); + ecs_set(world, Prefab, Position, {10, 20}); + + test_int(dummy_invoked, 0); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemPeriodic.c b/fggl/ecs2/flecs/test/api/src/SystemPeriodic.c new file mode 100644 index 0000000000000000000000000000000000000000..b41f7decfa78e6168341bdd654b6d55602872b65 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemPeriodic.c @@ -0,0 +1,2008 @@ +#include <api.h> + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + Velocity *v = NULL; + Mass *m = NULL; + + if (it->column_count >= 2) { + if (ecs_term_size(it, 2) == sizeof(Velocity)) { + v = ecs_term(it, Velocity, 2); + } + } + + if (it->column_count >= 3) { + if (ecs_term_size(it, 3) == sizeof(Mass)) { + m = ecs_term(it, Mass, 3); + } + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + + if (v) { + v[i].x = 30; + v[i].y = 40; + } + + if (m) { + m[i] = 50; + } + } +} + +void SystemPeriodic_1_type_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void SystemPeriodic_1_type_3_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position, Velocity, Mass); + ECS_ENTITY(world, e2, Position, Velocity, Mass); + ECS_ENTITY(world, e3, Position, Velocity, Mass); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Velocity, Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e3, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + + const Mass *m = ecs_get(world, e1, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + m = ecs_get(world, e2, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + m = ecs_get(world, e3, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + ecs_fini(world); +} + +void SystemPeriodic_3_type_1_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Mass); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[2][0], ecs_id(Position)); + test_int(ctx.s[2][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void SystemPeriodic_2_type_3_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity, Mass); + ECS_ENTITY(world, e2, Position, Velocity, Mass); + ECS_ENTITY(world, e3, Position, Velocity, Mass, Rotation); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Velocity, Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], 0); + test_int(ctx.c[1][2], ecs_id(Mass)); + test_int(ctx.s[1][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e3, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + + const Mass *m = ecs_get(world, e1, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + m = ecs_get(world, e2, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + m = ecs_get(world, e3, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + ecs_fini(world); +} + +void SystemPeriodic_1_type_1_component_1_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position, Tag); + ECS_ENTITY(world, e2, Position, Tag); + ECS_ENTITY(world, e3, Position, Tag); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Tag); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], Tag); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void SystemPeriodic_2_type_1_component_1_tag() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Tag, 0); + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position, Tag); + ECS_ENTITY(world, e2, Position, Tag); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Tag); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], Tag); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + + +void SystemPeriodic_2_type_1_and_1_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, !Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void SystemPeriodic_2_type_2_and_1_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity, Mass); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Velocity, !Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_fini(world); +} + +void SystemPeriodic_2_type_2_and_2_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_COMPONENT(world, Rotation); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity, Rotation); + ECS_ENTITY(world, e3, Position, Velocity, Mass); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Velocity, !Mass, !Rotation); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 4); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + test_int(ctx.c[0][3], ecs_id(Rotation)); + test_int(ctx.s[0][3], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_fini(world); +} + +typedef Position Position_1; +typedef Position Position_2; + +void SystemPeriodic_4_type_1_and_1_or() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Position_1); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position_1, Velocity); + ECS_ENTITY(world, e3, Position, Position_1, Velocity); + ECS_ENTITY(world, e4, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position || Position_1, Velocity); + + ecs_set(world, e3, Position_1, {0, 0}); + ecs_set(world, e4, Velocity, {0, 0}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[1][0], ecs_id(Position_1)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], 0); + test_int(ctx.c[2][0], ecs_id(Position)); + test_int(ctx.s[2][0], 0); + test_int(ctx.c[2][1], ecs_id(Velocity)); + test_int(ctx.s[2][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position_1); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position_1); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e3, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e4, Velocity); + test_assert(v != NULL); + test_int(v->x, 0); + test_int(v->y, 0); + + ecs_fini(world); +} + +void SystemPeriodic_4_type_1_and_1_or_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Position_1); + ECS_COMPONENT(world, Position_2); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position_2, Velocity); + ECS_ENTITY(world, e3, Position_1, Position_2, Velocity); + ECS_ENTITY(world, e4, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position || Position_1 || Position_2, Velocity); + + ecs_set(world, e3, Position_2, {0, 0}); + ecs_set(world, e4, Velocity, {0, 0}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 3); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[1][0], ecs_id(Position_2)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], 0); + test_int(ctx.c[2][0], ecs_id(Position_1)); + test_int(ctx.s[2][0], 0); + test_int(ctx.c[2][1], ecs_id(Velocity)); + test_int(ctx.s[2][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position_2); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position_1); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position_2); + test_assert(p != NULL); + test_int(p->x, 0); + test_int(p->y, 0); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e3, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e4, Velocity); + test_assert(v != NULL); + test_int(v->x, 0); + test_int(v->y, 0); + + ecs_fini(world); +} + +void SystemPeriodic_1_type_1_and_1_or() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Position_1); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position || Position_1, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + /* Now repeat test, with entities that have Position_1 */ + ctx = (Probe){0}; + + ecs_delete(world, e1); + ecs_delete(world, e2); + + ECS_ENTITY(world, e3, Position_1, Velocity); + ECS_ENTITY(world, e4, Position_1, Velocity); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.e[1], e4); + test_int(ctx.c[0][0], ecs_id(Position_1)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemPeriodic_2_type_1_and_1_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.e[1], e1); + test_int(ctx.e[2], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + ecs_fini(world); +} + +void SystemPeriodic_2_type_2_and_1_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position, Velocity, Mass); + ECS_ENTITY(world, e2, Position, Velocity, Mass); + ECS_ENTITY(world, e3, Position, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Velocity, ?Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + probe_has_entity(&ctx, e1); + probe_has_entity(&ctx, e2); + probe_has_entity(&ctx, e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], 0); + test_int(ctx.c[1][2], ecs_id(Mass)); + test_int(ctx.s[1][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e3, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + const Mass *m = ecs_get(world, e1, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + ecs_fini(world); +} + +void SystemPeriodic_6_type_1_and_2_optional() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity, Mass); + ECS_ENTITY(world, e3, Position, Mass); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Velocity); + ECS_ENTITY(world, e6, Mass); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Velocity, ?Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 4); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + probe_has_entity(&ctx, e1); + probe_has_entity(&ctx, e2); + probe_has_entity(&ctx, e3); + probe_has_entity(&ctx, e4); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], 0); + test_int(ctx.c[1][2], ecs_id(Mass)); + test_int(ctx.s[1][2], 0); + test_int(ctx.c[2][0], ecs_id(Position)); + test_int(ctx.s[2][0], 0); + test_int(ctx.c[2][1], ecs_id(Velocity)); + test_int(ctx.s[2][1], 0); + test_int(ctx.c[2][2], ecs_id(Mass)); + test_int(ctx.s[2][2], 0); + test_int(ctx.c[3][0], ecs_id(Position)); + test_int(ctx.s[3][0], 0); + test_int(ctx.c[3][1], ecs_id(Velocity)); + test_int(ctx.s[3][1], 0); + test_int(ctx.c[3][2], ecs_id(Mass)); + test_int(ctx.s[3][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); + + const Mass *m = ecs_get(world, e2, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + m = ecs_get(world, e3, Mass); + test_assert(m != NULL); + test_int(*m, 50); + + ecs_fini(world); +} + +static void Dummy_1(ecs_iter_t *it) { probe_system(it); } +static void Dummy_2(ecs_iter_t *it) { probe_system(it); } + +void SystemPeriodic_match_2_systems_w_populated_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, Dummy_1, EcsOnUpdate, Position); + ECS_SYSTEM(world, Dummy_2, EcsOnUpdate, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.column_count, 1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + test_int(ctx.e[0], e); + test_int(ctx.e[1], e); + + ecs_fini(world); +} + +void TestOptional_w_column(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + test_assert(p != NULL); + test_assert(v == NULL); + + probe_system(it); +} + +void TestOptional_w_shared(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + test_assert(p != NULL); + test_assert(v == NULL); + + probe_system(it); +} + +void SystemPeriodic_ensure_optional_is_unset_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, TestOptional_w_column, EcsOnUpdate, Position, ?Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + test_int(ctx.e[0], e); + + ecs_fini(world); +} + +void SystemPeriodic_ensure_optional_is_null_shared() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, TestOptional_w_shared, EcsOnUpdate, Position, ?PARENT:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + test_int(ctx.e[0], e); + + ecs_fini(world); +} + +static int on_period_count; +static int normal_count; +static int normal_count_2; +static int normal_count_3; + +static +void OnPeriodSystem(ecs_iter_t *it) { + on_period_count ++; +} + +static +void NormalSystem(ecs_iter_t *it) { + normal_count ++; +} + +static +void NormalSystem2(ecs_iter_t *it) { + normal_count_2 ++; +} + +static +void NormalSystem3(ecs_iter_t *it) { + normal_count_3 ++; +} + +void SystemPeriodic_on_period() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, OnPeriodSystem, EcsOnUpdate, Position); + ECS_SYSTEM(world, NormalSystem, EcsOnUpdate, Position); + + ecs_set_interval(world, OnPeriodSystem, 0.5); + + ecs_set_target_fps(world, 60); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + double start, now = 0; + + /* Run for one second */ + int count = 0; + do { + ecs_progress(world, 0); + if (!count) { + start = stats->delta_time; + } + + now += stats->delta_time; + count ++; + } while ((now - start) < 1.0); + + test_int(count, normal_count); + test_int(on_period_count, 2); + + ecs_fini(world); +} + +void SystemPeriodic_on_period_long_delta() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, OnPeriodSystem, EcsOnUpdate, Position); + ECS_SYSTEM(world, NormalSystem, EcsOnUpdate, Position); + + ecs_set_interval(world, OnPeriodSystem, 0.5); + + ecs_progress(world, 0); + + test_int(on_period_count, 0); + + ecs_sleepf(1.2); + + ecs_progress(world, 0); + + test_int(on_period_count, 1); + + ecs_sleepf(0.5); + + ecs_progress(world, 0); + + test_int(on_period_count, 2); + + ecs_fini(world); +} + +void SystemPeriodic_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, NormalSystem, EcsOnUpdate, Position); + + ecs_progress(world, 0); + + test_int(normal_count, 1); + + ecs_enable(world, NormalSystem, false); + + ecs_progress(world, 0); + + test_int(normal_count, 1); + + ecs_enable(world, NormalSystem, true); + + ecs_progress(world, 0); + + test_int(normal_count, 2); + + ecs_fini(world); +} + +void SystemPeriodic_disabled_feature() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, NormalSystem, EcsOnUpdate, Position); + ECS_SYSTEM(world, NormalSystem2, EcsOnUpdate, Position); + + ECS_TYPE(world, Type, NormalSystem, NormalSystem2); + + ecs_progress(world, 0); + + test_int(normal_count, 1); + test_int(normal_count_2, 1); + + ecs_enable(world, Type, false); + + ecs_progress(world, 0); + + test_int(normal_count, 1); + test_int(normal_count_2, 1); + + ecs_enable(world, Type, true); + + ecs_progress(world, 0); + + test_int(normal_count, 2); + test_int(normal_count_2, 2); + + ecs_fini(world); +} + +void SystemPeriodic_disabled_nested_feature() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, NormalSystem, EcsOnUpdate, Position); + ECS_SYSTEM(world, NormalSystem2, EcsOnUpdate, Position); + ECS_SYSTEM(world, NormalSystem3, EcsOnUpdate, Position); + + ECS_TYPE(world, NestedType, NormalSystem2, NormalSystem3); + ECS_TYPE(world, Type, NormalSystem, NestedType); + + ecs_progress(world, 0); + + test_int(normal_count, 1); + test_int(normal_count_2, 1); + test_int(normal_count_3, 1); + + ecs_enable(world, Type, false); + + ecs_progress(world, 0); + + test_int(normal_count, 1); + test_int(normal_count_2, 1); + test_int(normal_count_3, 1); + + ecs_enable(world, Type, true); + + ecs_progress(world, 0); + + test_int(normal_count, 2); + test_int(normal_count_2, 2); + test_int(normal_count_3, 2); + + ecs_fini(world); +} + +void TwoRefs(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ECS_COLUMN(it, Velocity, v, 2); + + test_assert(!ecs_is_owned(it, 1)); + test_assert(!ecs_is_owned(it, 2)); + + (void)p; + (void)v; + + probe_system(it); +} + +void SystemPeriodic_two_refs() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e, Position, Velocity); + ECS_ENTITY(world, E2, Mass); + + ECS_SYSTEM(world, TwoRefs, EcsOnUpdate, e:Position, e:Velocity, :e, Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 4); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], e); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], e); + test_int(ctx.c[0][2], e); + test_int(ctx.s[0][2], 0); + test_int(ctx.c[0][3], ecs_id(Mass)); + test_int(ctx.s[0][3], 0); + + test_int(ctx.e[0], E2); + + ecs_fini(world); +} + +void SystemPeriodic_filter_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Entity1, Position); + ECS_ENTITY(world, Entity2, Position, Disabled); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 1); + test_int(ctx.e[0], Entity1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemPeriodic_match_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Entity1, Position); + ECS_ENTITY(world, Entity2, Position, Disabled); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Disabled); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.e[0], Entity2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], EcsDisabled); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemPeriodic_match_disabled_and_enabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Entity1, Position); + ECS_ENTITY(world, Entity2, Position, Disabled); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Disabled); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.e[0], Entity1); + test_int(ctx.e[1], Entity2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], EcsDisabled); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemPeriodic_match_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Entity1, Position); + ECS_ENTITY(world, Entity2, Position, Prefab); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, Prefab); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.e[0], Entity2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], EcsPrefab); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemPeriodic_match_prefab_and_normal() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Entity1, Position); + ECS_ENTITY(world, Entity2, Position, Prefab); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, ?Prefab); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.e[0], Entity1); + test_int(ctx.e[1], Entity2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], EcsPrefab); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +static +void TestIsSharedOnNotSet(ecs_iter_t *it) { + test_assert(ecs_is_owned(it, 2) != false); +} + +void SystemPeriodic_is_shared_on_column_not_set() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, Entity, Position); + + ECS_SYSTEM(world, TestIsSharedOnNotSet, EcsOnUpdate, Position, ?Velocity); + + ecs_progress(world, 0); + + ecs_fini(world); +} + + +void SystemPeriodic_owned_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, (IsA, base)); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, OWNED:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.e[0], e1); + + ecs_fini(world); +} + +void SystemPeriodic_owned_not_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, (IsA, base)); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, !OWNED:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.e[0], e2); + + ecs_fini(world); +} + +void OwnedOr(ecs_iter_t *it) { + probe_system(it); +} + +void SystemPeriodic_owned_or_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Mass); + ECS_ENTITY(world, e3, Position, (IsA, base)); + + ECS_SYSTEM(world, OwnedOr, EcsOnUpdate, Position, OWNED:Velocity || OWNED:Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.e[0], e1); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Mass)); + test_int(ctx.s[1][1], 0); + test_int(ctx.e[1], e2); + + ecs_fini(world); +} + +void SystemPeriodic_shared_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, (IsA, base)); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, SHARED:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], base); + test_int(ctx.e[0], e2); + + ecs_fini(world); +} + +void SystemPeriodic_shared_not_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, (IsA, base)); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, !SHARED:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.e[0], e1); + + ecs_fini(world); +} + +void SharedOr(ecs_iter_t *it) { + probe_system(it); +} + +void SystemPeriodic_shared_or_column() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, base1, Velocity); + ECS_ENTITY(world, base2, Mass); + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Mass); + ECS_ENTITY(world, e3, Position, (IsA, base1)); + ECS_ENTITY(world, e4, Position, (IsA, base2)); + + ECS_SYSTEM(world, SharedOr, EcsOnUpdate, Position, SHARED:Velocity || SHARED:Mass); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], base1); + test_int(ctx.e[0], e3); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Mass)); + test_int(ctx.s[1][1], base2); + test_int(ctx.e[1], e4); + + ecs_fini(world); +} + +void SystemPeriodic_container_dont_match_inheritance() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, (IsA, base)); + ECS_ENTITY(world, e2, Position, (ChildOf, base)); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, PARENT:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], base); + test_int(ctx.e[0], e2); + + ecs_fini(world); +} + +void SystemPeriodic_cascade_dont_match_inheritance() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, base, Velocity); + ECS_ENTITY(world, e1, Position, (IsA, base)); + ECS_ENTITY(world, e2, Position, (ChildOf, base)); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, CASCADE:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.column_count, 2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Velocity)); + test_int(ctx.s[1][1], base); + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + + ecs_fini(world); +} + +void SystemPeriodic_not_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e, Position); + ECS_ENTITY(world, e2, 0); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, !e2:Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + + ecs_add(world, e2, Velocity); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + + ecs_fini(world); +} + +static +void TestContext(ecs_iter_t *it) { + void *world_ctx = ecs_get_context(it->world); + test_assert(world_ctx == it->ctx); + int32_t *ctx = it->ctx; + (*ctx) ++; +} + +void SystemPeriodic_sys_context() { + ecs_world_t *world = ecs_init(); + int32_t param = 0; + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TestContext, EcsOnUpdate, Position); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {TestContext}, .ctx = ¶m + }); + + test_assert(ecs_get_system_ctx(world, TestContext) == ¶m); + + ecs_fini(world); +} + +void SystemPeriodic_get_sys_context_from_param() { + ecs_world_t *world = ecs_init(); + int32_t param = 0; + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, e, Position); + + ECS_SYSTEM(world, TestContext, EcsOnUpdate, Position); + + /* Set world context so system can compare if pointer is correct */ + ecs_set_context(world, ¶m); + + ecs_system_init(world, &(ecs_system_desc_t){ + .entity = {TestContext}, .ctx = ¶m + }); + + ecs_progress(world, 1); + + test_int(param, 1); + + ecs_fini(world); +} + +static ecs_entity_t dummy_invoked = 0; + +static void Dummy(ecs_iter_t *it) { + test_assert(dummy_invoked == 0); + dummy_invoked = it->entities[0]; +} + +void SystemPeriodic_owned_only() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, OWNED:Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 0); + + test_assert(dummy_invoked == e); + + ecs_fini(world); +} + +static void AssertReadonly(ecs_iter_t *it) { + test_assert(dummy_invoked == 0); + dummy_invoked = it->entities[0]; + + test_assert( ecs_is_readonly(it, 1) == true); +} + +void SystemPeriodic_shared_only() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, AssertReadonly, EcsOnUpdate, SHARED:Position); + + ecs_entity_t base = ecs_new(world, Position); + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); + + ecs_progress(world, 0); + + test_assert(dummy_invoked == e); + + ecs_fini(world); +} + +void SystemPeriodic_is_in_readonly() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, AssertReadonly, EcsOnUpdate, [in] Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 0); + + test_assert(dummy_invoked == e); + + ecs_fini(world); +} + +void SystemPeriodic_get_period() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, [in] Position); + + ecs_set_interval(world, Dummy, 10.0); + + test_flt( ecs_get_interval(world, Dummy), 10.0); + + ecs_fini(world); +} + +void TypeSystem(ecs_iter_t *it) { + probe_system(it); +} + +void SystemPeriodic_and_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, MyType, Position, Velocity); + + ECS_SYSTEM(world, TypeSystem, EcsOnUpdate, AND | MyType); + + ecs_new(world, Position); + ecs_new(world, Velocity); + ecs_entity_t e3 = ecs_new(world, MyType); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, TypeSystem); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.c[0][0], MyType); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemPeriodic_or_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, MyType, Position, Velocity); + + ECS_SYSTEM(world, TypeSystem, EcsOnUpdate, OR | MyType); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Velocity); + ecs_entity_t e3 = ecs_new(world, MyType); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 3); + test_int(ctx.system, TypeSystem); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], MyType); + test_int(ctx.c[1][0], MyType); + test_int(ctx.c[2][0], MyType); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/SystemUnSet.c b/fggl/ecs2/flecs/test/api/src/SystemUnSet.c new file mode 100644 index 0000000000000000000000000000000000000000..c151618b681d3ca81a40d7d723f028b78863a72b --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/SystemUnSet.c @@ -0,0 +1,599 @@ +#include <api.h> + +static +void UnSet(ecs_iter_t *it) { + probe_system(it); +} + +static +void UnSetA(ecs_iter_t *it) { + +} + +static +void UnSetB(ecs_iter_t *it) { + +} + +void SystemUnSet_unset_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 0); + + ctx = (Probe){ 0 }; + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void SystemUnSet_unset_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Velocity); + test_int(ctx.invoked, 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 0); + + ctx = (Probe){ 0 }; + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 0); +} + +void SystemUnSet_unset_1_of_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Velocity, {1, 2}); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Mass, {1}); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Mass); + test_int(ctx.invoked, 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Velocity); + test_int(ctx.invoked, 0); + + ctx = (Probe){ 0 }; + + ecs_remove(world, e, Position); + test_int(ctx.invoked, 0); + + ctx = (Probe){ 0 }; + + ecs_progress(world, 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 0); +} + +void SystemUnSet_unset_on_delete_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_delete(world, e); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void SystemUnSet_unset_on_delete_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add(world, e1, Velocity); + test_int(ctx.invoked, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + test_int(ctx.invoked, 0); + + ecs_entity_t e3 = ecs_new(world, Position); + ecs_add(world, e3, Velocity); + test_int(ctx.invoked, 0); + + ecs_delete(world, e3); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void SystemUnSet_unset_on_delete_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add(world, e1, Velocity); + ecs_add(world, e1, Mass); + test_int(ctx.invoked, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e2, Mass); + test_int(ctx.invoked, 0); + + ecs_entity_t e3 = ecs_new(world, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + test_int(ctx.invoked, 0); + + ecs_delete(world, e3); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); + + ecs_fini(world); +} + +void SystemUnSet_unset_on_fini_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_entity_t e3 = ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_new(world, Velocity); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); +} + +void SystemUnSet_unset_on_fini_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add(world, e1, Velocity); + test_int(ctx.invoked, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + test_int(ctx.invoked, 0); + + ecs_entity_t e3 = ecs_new(world, Position); + ecs_add(world, e3, Velocity); + test_int(ctx.invoked, 0); + + ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_new(world, Velocity); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); +} + +void SystemUnSet_unset_on_fini_3() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity, Mass); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_add(world, e1, Velocity); + ecs_add(world, e1, Mass); + test_int(ctx.invoked, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + ecs_add(world, e2, Velocity); + ecs_add(world, e2, Mass); + test_int(ctx.invoked, 0); + + ecs_entity_t e3 = ecs_new(world, Position); + ecs_add(world, e3, Velocity); + ecs_add(world, e3, Mass); + test_int(ctx.invoked, 0); + + ecs_new(world, Position); + test_int(ctx.invoked, 0); + + ecs_new(world, Velocity); + test_int(ctx.invoked, 0); + + ecs_new(world, Mass); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 3); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], 0); +} + +void SystemUnSet_overlapping_unset_systems() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, UnSetA, EcsUnSet, Position); + ECS_SYSTEM(world, UnSetB, EcsUnSet, Position, Velocity); + ECS_SYSTEM(world, UnSet, EcsUnSet, Position, Velocity); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + ecs_add(world, e, Velocity); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Velocity); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +static +void UnSet_TestComp(ecs_iter_t *it) { + if (!ecs_get_context(it->world)) { + return; + } + + probe_system(it); + + test_int(it->count, 1); + + Position *p = ecs_term(it, Position, 1); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void SystemUnSet_unset_move_to_nonempty_table() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, UnSet_TestComp, EcsUnSet, Position); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_new(world, Type); + test_int(ctx.invoked, 0); + + ecs_new(world, Type); + test_int(ctx.invoked, 0); + + ecs_entity_t e = ecs_new(world, Type); + ecs_set(world, e, Position, {10, 20}); + ecs_set(world, e, Velocity, {20, 10}); + test_int(ctx.invoked, 0); + + ecs_entity_t e2 = ecs_new(world, Velocity); + ecs_set(world, e2, Velocity, {30, 40}); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet_TestComp); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + /* Prevent system from getting called by fini */ + ecs_set_context(world, NULL); + + ecs_fini(world); +} + +static +void UnSet_WriteComp(ecs_iter_t *it) { + if (!ecs_get_context(it->world)) { + return; + } + + probe_system(it); + + test_int(it->count, 1); + + Position *p = ecs_term(it, Position, 1); + test_assert(p != NULL); + + Velocity *v = ecs_term(it, Velocity, 2); + + test_int(p->x, 10); + test_int(p->y, 20); + + test_int(v->x, 1); + test_int(v->y, 2); + + v->x = 2; + v->y = 3; +} + +void SystemUnSet_write_in_unset() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, UnSet_WriteComp, EcsUnSet, Position, Velocity); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + ecs_set(world, e, Velocity, {1, 2}); + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_remove(world, e, Position); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, UnSet_WriteComp); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + /* Prevent system from getting called by fini */ + ecs_set_context(world, NULL); + + const Velocity *v = ecs_get(world, e, Velocity); + test_int(v->x, 2); + test_int(v->y, 3); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/System_w_FromContainer.c b/fggl/ecs2/flecs/test/api/src/System_w_FromContainer.c new file mode 100644 index 0000000000000000000000000000000000000000..34168264ad495020364e2369f7781879f55863b0 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/System_w_FromContainer.c @@ -0,0 +1,1323 @@ +#include <api.h> + +void System_w_FromContainer_setup() { + ecs_tracing_enable(-3); +} + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m_ptr, 1); + bool shared = false; + + if (m_ptr) { + shared = !ecs_is_owned(it, 1); + } + + Position *p = NULL; + Velocity *v = NULL; + + if (it->column_count >= 2) { + p = ecs_term(it, Position, 2); + } + + if (it->column_count >= 3) { + v = ecs_term(it, Velocity, 3); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + Mass m = 1; + if (m_ptr) { + if (shared) { + m = *m_ptr; + } else { + m = m_ptr[i]; + } + } + + p[i].x = 10 * m; + p[i].y = 20 * m; + + if (v) { + v[i].x = 30 * m; + v[i].y = 40 * m; + } + } +} + +void System_w_FromContainer_1_column_from_container() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ecs_entity_t parent = ecs_set(world, 0, Mass, {2}); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + ecs_add_pair(world, e3, EcsChildOf, parent); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_2_column_1_from_container() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position, Velocity); + ECS_ENTITY(world, e2, Position, Velocity); + ECS_ENTITY(world, e3, Position, Velocity); + ECS_ENTITY(world, e4, Position, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position, Velocity); + + ecs_entity_t parent = ecs_set(world, 0, Mass, {2}); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + ecs_add_pair(world, e3, EcsChildOf, parent); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Velocity)); + test_int(ctx.s[0][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + const Velocity *v = ecs_get(world, e1, Velocity); + test_assert(v != NULL); + test_int(v->x, 60); + test_int(v->y, 80); + + v = ecs_get(world, e2, Velocity); + test_assert(v != NULL); + test_int(v->x, 60); + test_int(v->y, 80); + + v = ecs_get(world, e3, Velocity); + test_assert(v != NULL); + test_int(v->x, 60); + test_int(v->y, 80); + + ecs_fini(world); +} + +static +void Iter_2_shared(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m_ptr, 1); + + Rotation *r_ptr = NULL; + Position *p = NULL; + Velocity *v = NULL; + + if (it->column_count >= 2) { + r_ptr = ecs_term(it, Rotation, 2); + } + + if (it->column_count >= 3) { + p = ecs_term(it, Position, 3); + } + + if (it->column_count >= 4) { + v = ecs_term(it, Velocity, 4); + } + + probe_system(it); + + Mass m = 1; + if (m_ptr) { + m = *m_ptr; + } + + Rotation r = 0; + if (r_ptr) { + r = *r_ptr; + } + + int i; + for (i = 0; i < it->count; i ++) { + if (p) { + p[i].x = 10 * m + r; + p[i].y = 20 * m + r; + } + + if (v) { + v[i].x = 30 * m + r; + v[i].y = 40 * m + r; + } + } +} + +void System_w_FromContainer_3_column_2_from_container() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter_2_shared, EcsOnUpdate, PARENT:Mass, PARENT:Rotation, Position); + + ecs_entity_t parent = ecs_set(world, 0, Mass, {2}); + ecs_set(world, parent, Rotation, {3}); + + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + ecs_add_pair(world, e4, EcsChildOf, parent); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter_2_shared); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e4); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Rotation)); + test_int(ctx.s[0][1], parent); + test_int(ctx.c[0][2], ecs_id(Position)); + test_int(ctx.s[0][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 23); + test_int(p->y, 43); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 23); + test_int(p->y, 43); + + p = ecs_get(world, e4, Position); + test_assert(p != NULL); + test_int(p->x, 23); + test_int(p->y, 43); + + ecs_fini(world); +} + +void System_w_FromContainer_3_column_2_from_different_container() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + ECS_ENTITY(world, e6, Position); + + ECS_SYSTEM(world, Iter_2_shared, EcsOnUpdate, PARENT:Mass, PARENT:Rotation, Position); + + ecs_entity_t parent_1 = ecs_set(world, 0, Mass, {2}); + ecs_entity_t parent_2 = ecs_set(world, 0, Rotation, {3}); + + ecs_add_pair(world, e1, EcsChildOf, parent_1); + ecs_add_pair(world, e1, EcsChildOf, parent_2); + + ecs_add_pair(world, e2, EcsChildOf, parent_1); + ecs_add_pair(world, e2, EcsChildOf, parent_2); + + ecs_add_pair(world, e3, EcsChildOf, parent_1); + ecs_add_pair(world, e3, EcsChildOf, parent_2); + + /* e4 and e5 should not be matched */ + ecs_add_pair(world, e4, EcsChildOf, parent_1); + ecs_add_pair(world, e5, EcsChildOf, parent_2); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter_2_shared); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent_1); + test_int(ctx.c[0][1], ecs_id(Rotation)); + test_int(ctx.s[0][1], parent_2); + test_int(ctx.c[0][2], ecs_id(Position)); + test_int(ctx.s[0][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 23); + test_int(p->y, 43); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 23); + test_int(p->y, 43); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 23); + test_int(p->y, 43); + + ecs_fini(world); +} + +void System_w_FromContainer_2_column_1_from_container_w_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, !PARENT:Mass, Position); + + ecs_entity_t parent_1 = ecs_set(world, 0, Mass, {2}); + ecs_entity_t parent_2 = ecs_set(world, 0, Rotation, {3}); + + ecs_add_pair(world, e1, EcsChildOf, parent_2); + ecs_add_pair(world, e2, EcsChildOf, parent_2); + ecs_add_pair(world, e3, EcsChildOf, parent_2); + ecs_add_pair(world, e4, EcsChildOf, parent_1); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 2); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e5); + test_int(ctx.e[1], e1); + test_int(ctx.e[2], e2); + test_int(ctx.e[3], e3); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[1][0], ecs_id(Mass)); + test_int(ctx.s[1][0], 0); + test_int(ctx.c[1][1], ecs_id(Position)); + test_int(ctx.s[1][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + p = ecs_get(world, e5, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void System_w_FromContainer_3_column_1_from_comtainer_1_from_container_w_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + ECS_ENTITY(world, e6, Position); + + ECS_SYSTEM(world, Iter_2_shared, EcsOnUpdate, !PARENT:Mass, PARENT:Rotation, Position); + + ecs_entity_t parent_1 = ecs_set(world, 0, Mass, {2}); + ecs_entity_t parent_2 = ecs_set(world, 0, Rotation, {3}); + ecs_entity_t parent_3 = ecs_set(world, 0, Mass, {4}); + ecs_set(world, parent_3, Rotation, {5}); + + ecs_add_pair(world, e1, EcsChildOf, parent_2); + ecs_add_pair(world, e2, EcsChildOf, parent_2); + ecs_add_pair(world, e3, EcsChildOf, parent_2); + ecs_add_pair(world, e4, EcsChildOf, parent_1); + ecs_add_pair(world, e5, EcsChildOf, parent_3); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter_2_shared); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Rotation)); + test_int(ctx.s[0][1], parent_2); + test_int(ctx.c[0][2], ecs_id(Position)); + test_int(ctx.s[0][2], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 13); + test_int(p->y, 23); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 13); + test_int(p->y, 23); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 13); + test_int(p->y, 23); + + ecs_fini(world); +} + +void System_w_FromContainer_2_column_1_from_container_w_not_prefab() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_PREFAB(world, Prefab, Rotation); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, !PARENT:Mass, Position); + + ecs_entity_t parent_1 = ecs_set(world, 0, Mass, {2}); + ecs_add_pair(world, e1, EcsIsA, Prefab); + + ecs_add_pair(world, e1, EcsChildOf, parent_1); + ecs_set(world, e1, Position, {1, 2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 1); + test_int(p->y, 2); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void System_w_FromContainer_2_column_1_from_container_w_or() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + ECS_ENTITY(world, e5, Position); + ECS_ENTITY(world, e6, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass || PARENT:Rotation, Position); + + ecs_entity_t parent_1 = ecs_set(world, 0, Mass, {2}); + ecs_entity_t parent_2 = ecs_set(world, 0, Rotation, {3}); + ecs_entity_t parent_3 = ecs_set(world, 0, Rotation, {4}); + ecs_set(world, parent_3, Mass, {5}); + ecs_entity_t parent_4 = ecs_set(world, 0, Velocity, {10, 20}); + + ecs_add_pair(world, e1, EcsChildOf, parent_1); + ecs_add_pair(world, e2, EcsChildOf, parent_2); + ecs_add_pair(world, e3, EcsChildOf, parent_3); + ecs_add_pair(world, e4, EcsChildOf, parent_4); + ecs_add_pair(world, e5, EcsChildOf, parent_2); + ecs_add_pair(world, e5, EcsChildOf, parent_3); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 4); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.e[3], e5); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent_1); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[1][0], ecs_id(Rotation)); + test_int(ctx.s[1][0], parent_2); + test_int(ctx.c[1][1], ecs_id(Position)); + test_int(ctx.s[2][1], 0); + test_int(ctx.c[2][0], ecs_id(Mass)); + test_int(ctx.s[2][0], parent_3); + test_int(ctx.c[2][1], ecs_id(Position)); + test_int(ctx.s[2][1], 0); + test_int(ctx.c[3][0], ecs_id(Mass)); + test_int(ctx.s[3][0], parent_3); + test_int(ctx.c[3][1], ecs_id(Position)); + test_int(ctx.s[3][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 60); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 50); + test_int(p->y, 100); + + p = ecs_get(world, e5, Position); + test_assert(p != NULL); + test_int(p->x, 50); + test_int(p->y, 100); + + ecs_fini(world); +} + +static +void Dummy(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m_ptr, 1); + ECS_COLUMN(it, Position, p, 2); + + test_assert(!m_ptr || !ecs_is_owned(it, 1)); + + probe_system(it); + + Mass m = 1; + if (m_ptr) { + m = *m_ptr; + } + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += m; + p[i].y += m; + } +} + +void System_w_FromContainer_add_component_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, parent, Mass, {2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_add_component_after_match_and_rematch() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* This will rematch tables, but not match Iter with e1 and e2 because the + * parent does not have Mass yet */ + ecs_progress(world, 1); + test_int(ctx.count, 0); + + ecs_set(world, parent, Mass, {2}); + + /* Now a rematch of tables need to happen again, since parent has changed */ + ctx = (Probe){0}; + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_add_component_after_match_and_rematch_w_entity_type_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ECS_ENTITY(world, Parent, 0); + ECS_ENTITY(world, e1, Position, (ChildOf, Parent)); + ECS_ENTITY(world, e2, Position, (ChildOf, Parent)); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* This will rematch tables, but not match Iter with e1 and e2 because the + * Parent does not have Mass yet */ + ecs_progress(world, 1); + test_int(ctx.count, 0); + + ecs_set(world, Parent, Mass, {2}); + + /* Now a rematch of tables need to happen again, since parent has changed */ + ctx = (Probe){0}; + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], Parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +static +void SetMass(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Mass, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Mass, {2}); + } +} + +void System_w_FromContainer_add_component_after_match_and_rematch_w_entity_type_expr_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, SetMass, EcsOnUpdate, Velocity, :Mass); + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ECS_ENTITY(world, Parent, Velocity); + ECS_ENTITY(world, e1, Position, (ChildOf, Parent)); + ECS_ENTITY(world, e2, Position, (ChildOf, Parent)); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* This will rematch tables, but not match Iter with e1 and e2 because the + * Parent does not have Mass yet */ + ecs_progress(world, 1); + test_int(ctx.count, 0); + + /* Now a rematch of tables need to happen again, since parent has changed */ + ctx = (Probe){0}; + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], Parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_add_component_after_match_unmatch() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, !PARENT:Mass, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, parent, Mass, {2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e3); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void System_w_FromContainer_add_component_after_match_unmatch_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + ECS_SYSTEM(world, Dummy, EcsOnUpdate, !PARENT:Mass, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, parent, Mass, {2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_add_component_after_match_2_systems() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + ECS_SYSTEM(world, Dummy, EcsOnUpdate, PARENT:Mass, Position); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, parent, Mass, {2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 4); + test_int(ctx.invoked, 2); + test_int(ctx.system, Dummy); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e1); + test_int(ctx.e[3], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 22); + test_int(p->y, 42); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 22); + test_int(p->y, 42); + + ecs_fini(world); +} + +static +void AddMass(ecs_iter_t *it) { + test_assert(it->ctx != NULL); + ecs_entity_t ecs_id(Mass) = *(ecs_entity_t*)it->ctx; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Mass, {2}); + } +} + +void System_w_FromContainer_add_component_in_progress_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + ECS_ENTITY(world, Tag, 0); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + ECS_TRIGGER(world, AddMass, EcsOnAdd, Tag); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity = {AddMass}, .ctx = &ecs_id(Mass) + }); + + ecs_entity_t parent = ecs_new(world, 0); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_add_id(world, parent, Tag); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_adopt_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ecs_entity_t parent = ecs_new(world, Mass); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + + ecs_set(world, parent, Mass, {2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromContainer_new_child_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e3, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ECS_ENTITY(world, parent, Mass); + ECS_ENTITY(world, e1, (ChildOf, parent), Position); + ECS_ENTITY(world, e2, (ChildOf, parent), Position); + + ecs_set(world, parent, Mass, {2}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void IterSame(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p_parent, 1); + Position *p = ecs_term(it, Position, 2); + + test_assert(!ecs_is_owned(it, 1)); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += p_parent->x; + p[i].y += p_parent->y; + } +} + +void System_w_FromContainer_select_same_from_container() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + ECS_ENTITY(world, e2, Position); + ECS_ENTITY(world, e3, Position); + ECS_ENTITY(world, e4, Position); + + ECS_SYSTEM(world, IterSame, EcsOnUpdate, PARENT:Position, Position); + + ecs_entity_t parent = ecs_set(world, 0, Position, {1, 2}); + ecs_add_pair(world, e1, EcsChildOf, parent); + ecs_add_pair(world, e2, EcsChildOf, parent); + ecs_add_pair(world, e3, EcsChildOf, parent); + + ecs_set(world, e1, Position, {10, 20}); + ecs_set(world, e2, Position, {20, 40}); + ecs_set(world, e3, Position, {30, 60}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, IterSame); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.e[1], e2); + test_int(ctx.e[2], e3); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 22); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 21); + test_int(p->y, 42); + + p = ecs_get(world, e3, Position); + test_assert(p != NULL); + test_int(p->x, 31); + test_int(p->y, 62); + + ecs_fini(world); +} + +void System_w_FromContainer_realloc_after_match() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, PARENT:Mass, Position); + + ecs_entity_t parent = ecs_set(world, 0, Mass, {2}); + ecs_add_pair(world, e1, EcsChildOf, parent); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 20); + test_int(p->y, 40); + + /* Add 1000 entities to table of container, which will trigger realloc */ + ecs_type_t parent_type = ecs_get_type(world, parent); + test_assert(parent_type != 0); + + ecs_bulk_new_w_type(world, parent_type, 1000); + + /* Change value of parent Mass. This will update the value in the new table. + * If the realloc would not be properly handled, the code could either crash + * or reference freed memory and use the old value. */ + ecs_set(world, parent, Mass, {3}); + + ctx = (Probe){0}; + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], parent); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 60); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/System_w_FromEntity.c b/fggl/ecs2/flecs/test/api/src/System_w_FromEntity.c new file mode 100644 index 0000000000000000000000000000000000000000..0c07506d6a25f3055206e149692a060b63f82bf4 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/System_w_FromEntity.c @@ -0,0 +1,141 @@ +#include <api.h> + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m_ptr, 1); + + Position *p = NULL; + Velocity *v = NULL; + + if (it->column_count >= 2) { + p = ecs_term(it, Position, 2); + } + + if (it->column_count >= 3) { + v = ecs_term(it, Velocity, 3); + } + + test_assert(!m_ptr || !ecs_is_owned(it, 1)); + + probe_system(it); + + Mass m = 1; + if (m_ptr) { + m = *m_ptr; + } + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10 * m; + p[i].y = 20 * m; + + if (v) { + v[i].x = 30 * m; + v[i].y = 40 * m; + } + } +} + +void System_w_FromEntity_2_column_1_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + + ECS_ENTITY(world, e1, Mass); + ECS_ENTITY(world, e2, Position); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, e1:Mass, Position); + + ecs_set(world, e1, Mass, {5}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.c[0][0], ecs_id(Mass)); + test_int(ctx.s[0][0], e1); + test_int(ctx.c[0][1], ecs_id(Position)); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 50); + test_int(p->y, 100); + + ecs_fini(world); +} + +static bool dummy_invoked = 0; +static ecs_entity_t dummy_component = 0; +static ecs_entity_t dummy_source = 0; + +static +void dummy_reset() { + dummy_invoked = false; + dummy_component = 0; + dummy_source = 0; +} + +static +void Dummy(ecs_iter_t *it) { + dummy_invoked = 1; + dummy_component = ecs_term_id(it, 1); + dummy_source = ecs_term_source(it, 1); +} + +void System_w_FromEntity_task_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, e1:Position); + + ecs_progress(world, 1); + + test_bool(dummy_invoked, true); + test_assert(dummy_component == ecs_id(Position)); + test_assert(dummy_source == e1); + + dummy_reset(); + ecs_remove(world, e1, Position); + + ecs_progress(world, 1); + test_bool(dummy_invoked, false); + + ecs_fini(world); +} + +void System_w_FromEntity_task_not_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, e1, Position); + + ECS_SYSTEM(world, Dummy, EcsOnUpdate, !e1:Position); + + ecs_progress(world, 1); + + test_bool(dummy_invoked, false); + + ecs_remove(world, e1, Position); + + ecs_progress(world, 1); + + test_bool(dummy_invoked, true); + test_assert(dummy_component == ecs_id(Position)); + test_assert(dummy_source == e1); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/System_w_FromId.c b/fggl/ecs2/flecs/test/api/src/System_w_FromId.c new file mode 100644 index 0000000000000000000000000000000000000000..a83f8f42cf24a3d1f6562691d88f2ed6fcba7961 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/System_w_FromId.c @@ -0,0 +1,107 @@ +#include <api.h> + +static +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + } +} + +void System_w_FromId_2_column_1_from_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, :Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + + ecs_fini(world); +} + +void System_w_FromId_3_column_2_from_id() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, :Velocity, :Rotation); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], 0); + test_int(ctx.c[0][2], ecs_id(Rotation)); + test_int(ctx.s[0][2], 0); + + ecs_fini(world); +} + +static +void CheckColumnType(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 2); + + test_assert(ecs_type(Position) == ecs_column_type(it, 1)); + + probe_system(it); +} + +void System_w_FromId_column_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Rotation); + + ECS_SYSTEM(world, CheckColumnType, EcsOnUpdate, Position, :Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_new(world, Position); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/System_w_FromSystem.c b/fggl/ecs2/flecs/test/api/src/System_w_FromSystem.c new file mode 100644 index 0000000000000000000000000000000000000000..454b8bdd5b50dfde8a9e9260586823aa359288cb --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/System_w_FromSystem.c @@ -0,0 +1,194 @@ +#include <api.h> + +void InitVelocity(ecs_iter_t *it) { + ECS_COLUMN(it, Velocity, v, 1); + + int i; + for (i = 0; i < it->count; i ++) { + v[i].x = 10; + v[i].y = 20; + } +} + +void InitMass(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m, 1); + int i; + for (i = 0; i < it->count; i ++) { + m[i] = 3; + } +} + +void Iter(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + Velocity *v = NULL; + Mass *m = NULL; + + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + test_assert(!ecs_is_owned(it, 2)); + } + + if (it->column_count >= 3) { + m = ecs_term(it, Mass, 3); + test_assert(!m || !ecs_is_owned(it, 3)); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x += v->x; + p[i].y += v->y; + + if (m) { + p[i].x += *m; + p[i].y += *m; + } + } +} + +void System_w_FromSystem_2_column_1_from_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TRIGGER(world, InitVelocity, EcsOnAdd, Velocity); + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, SYSTEM:Velocity); + + test_assert( ecs_has(world, Iter, Velocity)); + const Velocity *v = ecs_get(world, Iter, Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_set(world, 0, Position, {0, 0}); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], Iter); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_progress(world, 1); + + test_int(p->x, 20); + test_int(p->y, 40); + + ecs_fini(world); +} + +void System_w_FromSystem_3_column_2_from_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TRIGGER(world, InitVelocity, EcsOnAdd, Velocity); + ECS_TRIGGER(world, InitMass, EcsOnAdd, Mass); + ECS_SYSTEM(world, Iter, EcsOnUpdate, Position, SYSTEM:Velocity, SYSTEM:Mass); + + test_assert( ecs_has(world, Iter, Velocity)); + const Velocity *v = ecs_get(world, Iter, Velocity); + test_assert(v != NULL); + test_int(v->x, 10); + test_int(v->y, 20); + + test_assert( ecs_has(world, Iter, Mass)); + const Mass *m = ecs_get(world, Iter, Mass); + test_assert(m != NULL); + test_int(*m, 3); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_set(world, 0, Position, {0, 0}); + + ecs_progress(world, 1); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Iter); + test_int(ctx.column_count, 3); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][1], Iter); + test_int(ctx.c[0][2], ecs_id(Mass)); + test_int(ctx.s[0][2], Iter); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 13); + test_int(p->y, 23); + + ecs_progress(world, 1); + + test_int(p->x, 26); + test_int(p->y, 46); + + ecs_fini(world); +} + +void Iter_reactive(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + Velocity *v = it->param; + Mass *m = NULL; + + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + test_assert(!ecs_is_owned(it, 2)); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = v->x; + p[i].y = v->y; + + if (m) { + p[i].x = *m; + p[i].y = *m; + } + } +} + +void Dummy_1(ecs_iter_t *it) { } +void Dummy_2(ecs_iter_t *it) { } + +void System_w_FromSystem_auto_add_tag() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Dummy_1, EcsOnUpdate, Position, SYSTEM:Hidden); + ECS_SYSTEM(world, Dummy_2, 0, Position, SYSTEM:Hidden); + + test_assert( ecs_has_entity(world, Dummy_1, EcsHidden)); + test_assert( ecs_has_entity(world, Dummy_2, EcsHidden)); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Tasks.c b/fggl/ecs2/flecs/test/api/src/Tasks.c new file mode 100644 index 0000000000000000000000000000000000000000..f8c1934d7eff41590945d2dccff40ee995a0add3 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Tasks.c @@ -0,0 +1,148 @@ +#include <api.h> + +void Task(ecs_iter_t *it) { + probe_system(it); +} + +void Tasks_no_components() { + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, Task, EcsOnUpdate, 0); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 0); + + ecs_fini(world); +} + +void Tasks_one_tag() { + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, Task, EcsOnUpdate, SYSTEM:Hidden); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 1); + test_int(ctx.c[0][0], EcsHidden); + + ecs_fini(world); +} + +void Tasks_from_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, Task, EcsOnUpdate, SYSTEM:Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 1); + test_int(ctx.column_count, 1); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +static int phase_counter = 0; + +static +void OnLoadTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 0); + phase_counter ++; +} + +static +void PostLoadTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 1); + phase_counter ++; +} + +static +void PreUpdateTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 2); + phase_counter ++; +} + +static +void OnUpdateTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 3); + phase_counter ++; +} + +static +void OnValidateTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 4); + phase_counter ++; +} + +static +void PostUpdateTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 5); + phase_counter ++; +} + +static +void PreStoreTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 6); + phase_counter ++; +} + +static +void OnStoreTask(ecs_iter_t *it) { + test_assert(it->entities == NULL); + test_int(it->count, 0); + test_int(phase_counter, 7); + phase_counter ++; +} + +void Tasks_tasks_in_phases() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnLoadTask, EcsOnLoad, :Position); + ECS_SYSTEM(world, PostLoadTask, EcsPostLoad, :Position); + ECS_SYSTEM(world, PreUpdateTask, EcsPreUpdate, :Position); + ECS_SYSTEM(world, OnUpdateTask, EcsOnUpdate, :Position); + ECS_SYSTEM(world, OnValidateTask, EcsOnValidate, :Position); + ECS_SYSTEM(world, PostUpdateTask, EcsPostUpdate, :Position); + ECS_SYSTEM(world, PreStoreTask, EcsPreStore, :Position); + ECS_SYSTEM(world, OnStoreTask, EcsOnStore, :Position); + + ecs_progress(world, 1); + + test_int(phase_counter, 8); + + ecs_fini(world); +} + diff --git a/fggl/ecs2/flecs/test/api/src/Timer.c b/fggl/ecs2/flecs/test/api/src/Timer.c new file mode 100644 index 0000000000000000000000000000000000000000..dc321c595951bdc32b69c813b576fe7be762dbeb --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Timer.c @@ -0,0 +1,613 @@ +#include <api.h> + +static bool system_a_invoked; +static bool system_b_invoked; +static bool system_c_invoked; + +static +void SystemA(ecs_iter_t *it) { + test_int(it->delta_time, 1.0); + test_int(it->delta_system_time, 3.0); + system_a_invoked = true; +} + +static +void SystemB(ecs_iter_t *it) { + test_int(it->delta_time, 1.0); + test_int(it->delta_system_time, 3.0); + system_b_invoked = true; +} + +void Timer_timeout() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t timer = ecs_set_timeout(world, SystemA, 3.0); + test_assert(timer != 0); + test_assert(timer == SystemA); + + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + + system_a_invoked = false; + + /* Make sure this was a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + ecs_fini(world); +} + +void Timer_interval() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t timer = ecs_set_interval(world, SystemA, 3.0); + test_assert(timer != 0); + test_assert(timer == SystemA); + + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + + system_a_invoked = false; + + /* Make sure this was not a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + + ecs_fini(world); +} + +void Timer_shared_timeout() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SystemB, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t timer = ecs_set_timeout(world, 0, 3.0); + test_assert(timer != 0); + + ecs_set_tick_source(world, SystemA, timer); + ecs_set_tick_source(world, SystemB, timer); + + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + test_bool(system_b_invoked, true); + + system_a_invoked = false; + system_b_invoked = false; + + /* Make sure this was a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + + ecs_fini(world); +} + +void Timer_shared_interval() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + ECS_SYSTEM(world, SystemB, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t timer = ecs_set_interval(world, 0, 3.0); + test_assert(timer != 0); + + ecs_set_tick_source(world, SystemA, timer); + ecs_set_tick_source(world, SystemB, timer); + + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + test_bool(system_b_invoked, true); + + system_a_invoked = false; + system_b_invoked = false; + + /* Make sure this was a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + test_bool(system_b_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + test_bool(system_b_invoked, true); + + ecs_fini(world); +} + +void Timer_start_stop_one_shot() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t timer = ecs_set_timeout(world, SystemA, 3.0); + test_assert(timer != 0); + test_assert(timer == SystemA); + + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + /* Stop timer */ + ecs_stop_timer(world, timer); + + /* Timer is stopped, should not trigger system */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + /* Start timer, this should reset timer */ + ecs_start_timer(world, timer); + + /* Make sure this was a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + + /* System should have been triggered */ + test_bool(system_a_invoked, true); + + ecs_fini(world); +} + +void Timer_start_stop_interval() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t timer = ecs_set_interval(world, SystemA, 3.0); + test_assert(timer != 0); + test_assert(timer == SystemA); + + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + /* Stop timer */ + ecs_stop_timer(world, timer); + + /* Timer is stopped, should not trigger system */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + /* Start timer, this should reset timer */ + ecs_start_timer(world, timer); + + /* Make sure this was a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + + /* System should have been triggered */ + test_bool(system_a_invoked, true); + + system_a_invoked = false; + + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + + /* Ensure that timer still triggers repeatedly */ + test_bool(system_a_invoked, true); + + ecs_fini(world); +} + +void Timer_rate_filter() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t filter = ecs_set_rate_filter(world, SystemA, 3, 0); + test_assert(filter != 0); + test_assert(filter == SystemA); + + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + + system_a_invoked = false; + + /* Make sure this was a one-shot timer */ + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, false); + ecs_progress(world, 1.0); + test_bool(system_a_invoked, true); + + ecs_fini(world); +} + +static +void SystemC(ecs_iter_t *it) { + test_int(it->delta_time, 1.0); + test_int(it->delta_system_time, 6.0); + system_c_invoked = true; +} + +void Timer_rate_filter_w_rate_filter_src() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemC, EcsOnUpdate, Position); + + ecs_new(world, Position); + + ecs_entity_t filter_a = ecs_set_rate_filter(world, 0, 2, 0); + test_assert(filter_a != 0); + + ecs_entity_t filter_b = ecs_set_rate_filter(world, SystemC, 3, filter_a); + test_assert(filter_b != 0); + test_assert(filter_b == SystemC); + + test_bool(system_c_invoked, false); + + int i; + for (i = 0; i < 5; i ++) { + ecs_progress(world, 1.0); + test_bool(system_c_invoked, false); + } + + ecs_progress(world, 1.0); + test_bool(system_c_invoked, true); + + system_c_invoked = false; + + /* Make sure rate filter triggers repeatedly */ + for (i = 0; i < 5; i ++) { + ecs_progress(world, 1.0); + test_bool(system_c_invoked, false); + } + + ecs_progress(world, 1.0); + test_bool(system_c_invoked, true); + + ecs_fini(world); +} + +void Timer_rate_filter_w_timer_src() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, SystemC, EcsOnUpdate, Position); + ECS_ENTITY(world, E1, Position); + + ecs_entity_t timer = ecs_set_interval(world, 0, 2.0); + test_assert(timer != 0); + + ecs_entity_t filter = ecs_set_rate_filter(world, SystemC, 3, timer); + test_assert(filter != 0); + test_assert(filter == SystemC); + + test_bool(system_c_invoked, false); + + int i; + for (i = 0; i < 5; i ++) { + ecs_progress(world, 1.0); + test_bool(system_c_invoked, false); + } + + ecs_progress(world, 1.0); + test_bool(system_c_invoked, true); + + system_c_invoked = false; + + /* Make sure rate filter triggers repeatedly */ + for (i = 0; i < 5; i ++) { + ecs_progress(world, 1.0); + test_bool(system_c_invoked, false); + } + + ecs_progress(world, 1.0); + test_bool(system_c_invoked, true); + + ecs_fini(world); +} + +void Timer_rate_filter_with_empty_src() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, SystemC, EcsOnUpdate, Position); + + ecs_new(world, Position); + + // Not actually a tick source + ecs_entity_t filter_a = ecs_new_id(world); + test_assert(filter_a != 0); + + ecs_entity_t filter_b = ecs_set_rate_filter(world, SystemC, 6, filter_a); + test_assert(filter_b != 0); + test_assert(filter_b == SystemC); + + test_bool(system_c_invoked, false); + + int i; + for (i = 0; i < 5; i ++) { + ecs_progress(world, 1.0); + test_bool(system_c_invoked, false); + } + + ecs_progress(world, 1.0); + test_bool(system_c_invoked, true); + + system_c_invoked = false; + + /* Make sure rate filter triggers repeatedly */ + for (i = 0; i < 5; i ++) { + ecs_progress(world, 1.0); + test_bool(system_c_invoked, false); + } + + ecs_progress(world, 1.0); + test_bool(system_c_invoked, true); + + ecs_fini(world); +} + +void Timer_one_shot_timer_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t timer = ecs_set_timeout(world, 0, 1.0); + + int i; + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0.3); + const EcsTickSource *src = ecs_get(world, timer, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + ecs_progress(world, 0.3); + const EcsTickSource *src = ecs_get(world, timer, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, true); + + /* Ensure timer doesn't tick again */ + for (i = 0; i < 12; i ++) { + ecs_progress(world, 0.3); + test_bool(src->tick, false); + } + + ecs_fini(world); +} + +void Timer_interval_timer_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t timer = ecs_set_interval(world, 0, 1.0); + + int i; + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0.3); + const EcsTickSource *src = ecs_get(world, timer, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + ecs_progress(world, 0.3); + const EcsTickSource *src = ecs_get(world, timer, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, true); + + for (i = 0; i < 2; i ++) { + ecs_progress(world, 0.3); + test_bool(src->tick, false); + } + + /* Timer should tick again */ + ecs_progress(world, 0.3); + test_bool(src->tick, true); + + ecs_fini(world); +} + +void Timer_rate_entity() { + ecs_world_t *world = ecs_init(); + + /* Specify 0 for source. This applies the rate to the frame ticks */ + ecs_entity_t rate = ecs_set_rate(world, 0, 4, 0); + + int i; + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, true); + + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + /* Filter should tick again */ + ecs_progress(world, 0); + test_bool(src->tick, true); + + ecs_fini(world); +} + +void Timer_nested_rate_entity() { + ecs_world_t *world = ecs_init(); + + /* Nested rate filter */ + ecs_entity_t parent = ecs_set_rate(world, 0, 2, 0); + ecs_entity_t rate = ecs_set_rate(world, 0, 2, parent); + + int i; + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, true); + + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + /* Filter should tick again */ + ecs_progress(world, 0); + test_bool(src->tick, true); + + ecs_fini(world); +} + +void Timer_nested_rate_entity_empty_src() { + ecs_world_t *world = ecs_init(); + + /* Rate filter with source that is not a tick source */ + ecs_entity_t parent = ecs_new(world, 0); + ecs_entity_t rate = ecs_set_rate(world, 0, 4, parent); + + int i; + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, true); + + for (i = 0; i < 3; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, rate, EcsTickSource); + test_assert(src != NULL); + test_bool(src->tick, false); + } + + /* Filter should tick again */ + ecs_progress(world, 0); + test_bool(src->tick, true); + + ecs_fini(world); +} + +void Timer_naked_tick_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t tick = ecs_set(world, 0, EcsTickSource, {0}); + + int i; + for (i = 0; i < 10; i ++) { + ecs_progress(world, 0); + const EcsTickSource *src = ecs_get(world, tick, EcsTickSource); + test_assert(src != NULL); + + /* Tick should always be true, no filter is applied */ + test_bool(src->tick, true); + } + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Trigger.c b/fggl/ecs2/flecs/test/api/src/Trigger.c new file mode 100644 index 0000000000000000000000000000000000000000..aec1e94eed45e2cacfc8f62f38d3266be9536590 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Trigger.c @@ -0,0 +1,1843 @@ +#include <api.h> + +static int on_remove_count = 0; + +void Trigger(ecs_iter_t *it) { + probe_system_w_ctx(it, it->ctx); +} + +void TriggerAdd(ecs_iter_t *it) { + ecs_id_t id = *(ecs_id_t*)it->ctx; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add_id(it->world, it->entities[i], id); + } +} + +void TriggerRemove(ecs_iter_t *it) { + ecs_id_t id = *(ecs_id_t*)it->ctx; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_remove_id(it->world, it->entities[i], id); + } +} + +void TriggerClear(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + ecs_clear(it->world, it->entities[i]); + } +} + +void TriggerDelete(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + ecs_delete(it->world, it->entities[i]); + } +} + +void Trigger_on_add_trigger_before_table() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + /* Create entity/table after trigger, should invoke trigger */ + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_add_trigger_after_table() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + /* Create entity/before trigger */ + ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + + /* Create trigger after table, should send notification to table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_remove_trigger_before_table() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + /* Create trigger after table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + /* Create entity/table after trigger, should invoke trigger */ + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagA); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_remove_trigger_after_table() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + /* Create entity/before trigger */ + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + /* Create trigger after table, should send notification to table */ + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_remove_id(world, e, TagA); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_add_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_add_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_on_add_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = EcsWildcard, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ctx = (Probe){0}; + + ecs_add_id(world, e, TagB); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagB); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagB); + + ecs_fini(world); +} + +void Trigger_on_add_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(Pred, Obj), + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Pred, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_pair(Pred, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, Obj)); + + ecs_fini(world); +} + +void Trigger_on_add_pair_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(Pred, EcsWildcard), + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Pred, ObjA)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_pair(Pred, ObjA)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, ObjA)); + + ctx = (Probe){0}; + + ecs_add_pair(world, e, Pred, ObjB); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_pair(Pred, ObjB)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, ObjB)); + + ecs_fini(world); +} + +void Trigger_on_add_pair_pred_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, PredA); + ECS_TAG(world, PredB); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, Obj), + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(PredA, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_pair(PredA, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(PredA, Obj)); + + ctx = (Probe){0}; + + ecs_add_pair(world, e, PredB, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_pair(PredB, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(PredB, Obj)); + + ecs_fini(world); +} + +void Trigger_on_add_pair_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, EcsWildcard), + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Pred, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, ecs_pair(Pred, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, Obj)); + + ecs_fini(world); +} + +void Trigger_on_remove_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagA); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_remove_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_on_remove_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = EcsWildcard, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA, TagB} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove(world, e, TagA); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ctx = (Probe){0}; + + ecs_remove_id(world, e, TagB); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagB); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagB); + + ecs_fini(world); +} + +void Trigger_on_remove_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(Pred, Obj), + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Pred, Obj)} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Pred, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_pair(Pred, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, Obj)); + + ecs_fini(world); +} + +void Trigger_on_remove_pair_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, ObjA); + ECS_TAG(world, ObjB); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(Pred, EcsWildcard), + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Pred, ObjA), ecs_pair(Pred, ObjB)} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Pred, ObjA); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_pair(Pred, ObjA)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, ObjA)); + + ctx = (Probe){0}; + + ecs_add_pair(world, e, Pred, ObjB); + + ecs_remove_pair(world, e, Pred, ObjB); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_pair(Pred, ObjB)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, ObjB)); + + ecs_fini(world); +} + +void Trigger_on_remove_pair_pred_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, PredA); + ECS_TAG(world, PredB); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, Obj), + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(PredA, Obj), ecs_pair(PredB, Obj)} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, PredA, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_pair(PredA, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(PredA, Obj)); + + ctx = (Probe){0}; + + ecs_remove_pair(world, e, PredB, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_pair(PredB, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(PredB, Obj)); + + ecs_fini(world); +} + +void Trigger_on_remove_pair_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pred); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, EcsWildcard), + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Pred, Obj)} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Pred, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, ecs_pair(Pred, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Pred, Obj)); + + ecs_fini(world); +} + +void Trigger_on_set_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_on_set_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = EcsWildcard, + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_set(world, e, Position, {10, 20}); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_on_set_pair() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(ecs_id(Position), Obj), + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(ecs_id(Position), Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_set_pair(world, e, Position, Obj, {10, 20}); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj)); + + ecs_fini(world); +} + +void Trigger_on_set_pair_w_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(ecs_id(Position), EcsWildcard), + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(ecs_id(Position), Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_set_pair(world, e, Position, Obj, {10, 20}); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj)); + + ecs_fini(world); +} + +void Trigger_on_set_pair_pred_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, Obj), + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(ecs_id(Position), Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_set_pair(world, e, Position, Obj, {10, 20}); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj)); + + ecs_fini(world); +} + +void Trigger_on_set_pair_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, EcsWildcard), + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(ecs_id(Position), Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_set_pair(world, e, Position, Obj, {10, 20}); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj)); + + ecs_fini(world); +} + +void Trigger_on_add_remove() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ctx = (Probe){0}; + + ecs_remove_id(world, e, TagA); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_set_component_after_modified() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_modified(world, e, Position); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_un_set_component() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsUnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsUnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_un_set_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = EcsWildcard, + .events = {EcsUnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_id(Position)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove(world, e, Position); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsUnSet); + test_int(ctx.event_id, ecs_id(Position)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + + ecs_fini(world); +} + +void Trigger_un_set_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(Rel, Obj), + .events = {EcsUnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Rel, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsUnSet); + test_int(ctx.event_id, ecs_pair(Rel, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Rel, Obj)); + + ecs_fini(world); +} + +void Trigger_un_set_pair_w_obj_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(Rel, EcsWildcard), + .events = {EcsUnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Rel, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsUnSet); + test_int(ctx.event_id, ecs_pair(Rel, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Rel, Obj)); + + ecs_fini(world); +} + +void Trigger_un_set_pair_pred_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, Obj), + .events = {EcsUnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Rel, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsUnSet); + test_int(ctx.event_id, ecs_pair(Rel, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Rel, Obj)); + + ecs_fini(world); +} + +void Trigger_un_set_pair_wildcard() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Rel); + ECS_TAG(world, Obj); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_pair(EcsWildcard, EcsWildcard), + .events = {EcsUnSet}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {ecs_pair(Rel, Obj)} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 0); + + ecs_remove_pair(world, e, Rel, Obj); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsUnSet); + test_int(ctx.event_id, ecs_pair(Rel, Obj)); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_pair(Rel, Obj)); + + ecs_fini(world); +} + +void Trigger_add_twice() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ctx = (Probe){0}; + + ecs_add_id(world, e, TagA); + + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Trigger_remove_twice() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_remove_id(world, e, TagA); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ctx = (Probe){0}; + + ecs_remove_id(world, e, TagA); + + test_int(ctx.invoked, 0); + + ecs_fini(world); +} + +void Trigger_on_remove_w_clear() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_clear(world, e); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_remove_w_delete() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_delete(world, e); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_on_remove_w_world_fini() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + test_int(ctx.invoked, 0); + + ecs_fini(world); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnRemove); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); +} + +void Trigger_on_add_w_clone() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], TagA); + + ctx = (Probe){0}; + + ecs_entity_t e2 = ecs_clone(world, 0, e, true); + + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, t); + test_int(ctx.event, EcsOnAdd); + test_int(ctx.event_id, TagA); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.c[0][0], TagA); + + ecs_fini(world); +} + +void Trigger_add_in_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = TriggerAdd, + .ctx = &TagB + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_assert(ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Trigger_remove_in_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = TriggerRemove, + .ctx = &TagB + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA, TagB} + }); + test_assert(e != 0); + + test_assert(!ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Trigger_clear_in_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = TriggerClear, + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA, TagB} + }); + test_assert(e != 0); + + test_assert(!ecs_has_id(world, e, TagA)); + test_assert(!ecs_has_id(world, e, TagB)); + + ecs_fini(world); +} + +void Trigger_delete_in_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = TriggerDelete, + }); + + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA, TagB} + }); + test_assert(e != 0); + + test_assert(!ecs_is_alive(world, e)); + + ecs_fini(world); +} + +void Trigger_trigger_w_named_entity() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + + /* Create trigger before table */ + Probe ctx = {0}; + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity.name = "MyTrigger", + .term.id = TagA, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + + /* Create entity/table after trigger, should invoke trigger */ + ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){ + .add = {TagA} + }); + test_assert(e != 0); + + test_int(ctx.invoked, 1); + + ecs_fini(world); +} + +typedef struct Self { + ecs_entity_t value; +} Self; + +void RemoveSelf(ecs_iter_t *it) { + Self *s = ecs_term(it, Self, 1); + ecs_id_t ecs_id(Self) = ecs_term_id(it, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_assert(s[i].value == it->entities[i]); + + const Self *ptr = ecs_get(it->world, it->entities[i], Self); + test_assert(ptr != NULL); + test_assert(ptr->value == it->entities[i]); + + // Set to 0 so that if an entity were to get matched twice, it wouldn't + // silently succeed. + s[i].value = 0; + on_remove_count ++; + } +} + +void Trigger_on_remove_tree() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Self); + + ECS_TRIGGER(world, RemoveSelf, EcsOnRemove, Self); + + ecs_entity_t root = ecs_new_id(world); + ecs_entity_t e1 = ecs_new_w_pair(world, EcsChildOf, root); + ecs_entity_t e2 = ecs_new_w_pair(world, EcsChildOf, e1); + ecs_entity_t e3 = ecs_new_w_pair(world, EcsChildOf, e1); + ecs_entity_t e4 = ecs_new_w_pair(world, EcsChildOf, e2); + ecs_entity_t e5 = ecs_new_w_pair(world, EcsChildOf, e2); + + ecs_entity_t e6 = ecs_new_w_pair(world, EcsChildOf, root); + ecs_entity_t e7 = ecs_new_w_pair(world, EcsChildOf, e6); + ecs_entity_t e8 = ecs_new_w_pair(world, EcsChildOf, e6); + ecs_entity_t e9 = ecs_new_w_pair(world, EcsChildOf, e7); + ecs_entity_t e10 = ecs_new_w_pair(world, EcsChildOf, e7); + + ecs_set(world, e1, Self, {e1}); + ecs_set(world, e2, Self, {e2}); + ecs_set(world, e3, Self, {e3}); + ecs_set(world, e4, Self, {e4}); + ecs_set(world, e5, Self, {e5}); + ecs_set(world, e6, Self, {e6}); + ecs_set(world, e7, Self, {e7}); + ecs_set(world, e8, Self, {e8}); + ecs_set(world, e9, Self, {e9}); + ecs_set(world, e10, Self, {e10}); + + ecs_delete(world, root); + + test_int(on_remove_count, 10); + + test_assert(!ecs_is_alive(world, e1)); + test_assert(!ecs_is_alive(world, e2)); + test_assert(!ecs_is_alive(world, e3)); + test_assert(!ecs_is_alive(world, e4)); + test_assert(!ecs_is_alive(world, e5)); + test_assert(!ecs_is_alive(world, e6)); + test_assert(!ecs_is_alive(world, e7)); + test_assert(!ecs_is_alive(world, e8)); + test_assert(!ecs_is_alive(world, e9)); + test_assert(!ecs_is_alive(world, e10)); + + ecs_fini(world); +} + +void Trigger_set_get_context() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int32_t ctx_a, ctx_b; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity.name = "MyTrigger", + .term.id = Tag, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx_a + }); + test_assert(t != 0); + + test_assert(ecs_get_trigger_ctx(world, t) == &ctx_a); + + test_assert(ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity.entity = t, + .ctx = &ctx_b + }) == t); + + test_assert(ecs_get_trigger_ctx(world, t) == &ctx_b); + + ecs_fini(world); +} + +void Trigger_set_get_binding_context() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + int32_t ctx_a, ctx_b; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity.name = "MyTrigger", + .term.id = Tag, + .events = {EcsOnAdd}, + .callback = Trigger, + .binding_ctx = &ctx_a + }); + test_assert(t != 0); + + test_assert(ecs_get_trigger_binding_ctx(world, t) == &ctx_a); + + test_assert(ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity.entity = t, + .binding_ctx = &ctx_b + }) == t); + + test_assert(ecs_get_trigger_binding_ctx(world, t) == &ctx_b); + + ecs_fini(world); +} + +void Trigger_trigger_w_self() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t self = ecs_new_id(world); + + Probe ctx = {0}; + ecs_entity_t system = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = Tag, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx, + .self = self + }); + + ecs_entity_t e = ecs_new_id(world); + ecs_add_id(world, e, Tag); + + test_int(ctx.count, 1); + test_assert(ctx.system == system); + test_assert(ctx.self == self); + + ecs_fini(world); +} + +static int ctx_value; +static +void ctx_free(void *ctx) { + test_assert(&ctx_value == ctx); + ctx_value ++; +} + +static int binding_ctx_value; +static +void binding_ctx_free(void *ctx) { + test_assert(&binding_ctx_value == ctx); + binding_ctx_value ++; +} + +void Trigger_delete_trigger_w_delete_ctx() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = Tag, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx_value, + .ctx_free = ctx_free, + .binding_ctx = &binding_ctx_value, + .binding_ctx_free = binding_ctx_free + }); + test_assert(t != 0); + + test_assert(ecs_get_trigger_ctx(world, t) == &ctx_value); + test_assert(ecs_get_trigger_binding_ctx(world, t) == &binding_ctx_value); + + ecs_delete(world, t); + + test_int(ctx_value, 1); + test_int(binding_ctx_value, 1); + + ecs_fini(world); +} + +void Trigger_trigger_w_index() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + Probe ctx = {0}; + ecs_entity_t t = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = Tag, + .term.index = 50, + .events = {EcsOnAdd}, + .callback = Trigger, + .ctx = &ctx + }); + test_assert(t != 0); + + ecs_new(world, Tag); + + test_int(ctx.invoked, 1); + test_int(ctx.term_index, 50); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/TriggerOnAdd.c b/fggl/ecs2/flecs/test/api/src/TriggerOnAdd.c new file mode 100644 index 0000000000000000000000000000000000000000..190d062907281269af64bb66ac9bf95ebd00ca90 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/TriggerOnAdd.c @@ -0,0 +1,1021 @@ +#include <api.h> + +void TriggerOnAdd_setup() { + ecs_tracing_enable(-3); +} + +void Init(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + Velocity *v = NULL; + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 10; + p[i].y = 20; + + if (v) { + v[i].x = 30; + v[i].y = 40; + } + } +} + +static +void Add_to_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component) { + ecs_add_id(it->world, it->entities[i], ctx->component); + + test_assert( !!ecs_get_type(it->world, it->entities[i])); + } + + if (ctx->component_2) { + ecs_add_id(it->world, it->entities[i], ctx->component_2); + } + + ctx->entity_count ++; + } +} + +static +void Remove_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (ctx->component) { + ecs_remove_id(it->world, e, ctx->component); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, e, ctx->component_2); + } + + ctx->entity_count ++; + } +} + +static +void Set_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + ecs_entity_t ecs_id(Rotation) = ctx->component; + + int i; + for (i = 0; i < it->count; i ++) { + ecs_set(it->world, it->entities[i], Rotation, {10 + it->entities[i]}); + ctx->entity_count ++; + } +} + +static bool dummy_called = false; + +static +void Dummy(ecs_iter_t *it) { + dummy_called = true; +} + +void TriggerOnAdd_new_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnAdd_new_match_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnAdd_new_no_match_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Velocity); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_add_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_add(world, e, Position); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnAdd_add_match_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_add(world, e, Type); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnAdd_add_no_match_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_add(world, e, Position); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_set_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_set(world, e, Position, {1, 2}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +void TriggerOnAdd_set_no_match_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_set(world, e, Position, {1, 2}); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_clone_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e2, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnAdd_clone_match_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + ecs_entity_t e1 = ecs_new(world, Type); + test_assert(e1 != 0); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e2 = ecs_clone(world, 0, e1, false); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e2, Position); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnAdd_add_again_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_add(world, e, Position); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_set_again_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_set(world, e, Position, {10, 20}); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_set(world, e, Position, {30, 40}); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_add_again_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Type); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_add(world, e, Type); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_new_w_count_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 3); + test_assert(ids != NULL); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, Init); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.e[1], ids[1]); + test_int(ctx.e[2], ids[2]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + int i; + for (i = 0; i < 3; i ++) { + const Position *p = ecs_get(world, ids[i], Position); + test_int(p->x, 10); + test_int(p->y, 20); + } + + ecs_fini(world); +} + +static +void AddVelocity(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + ecs_type_t v = it->ctx; + if (!v) { + v = ecs_column_type(it, 2); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 1; + p[i].y = 2; + ecs_add_type(it->world, it->entities[i], v); + } +} + +void TriggerOnAdd_override_after_add_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 2}); + + ECS_TRIGGER(world, AddVelocity, EcsOnAdd, Position); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity = {AddVelocity}, .ctx = (void*)ecs_type(Velocity) + }); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_add(world, e, Position); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, AddVelocity); + test_int(ctx.column_count, 1); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 1); + test_int(p->y, 2); + + ecs_fini(world); +} + +static +void OnSetPosition(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x ++; + p[i].y ++; + } +} + +void TriggerOnAdd_set_after_add_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TRIGGER(world, AddVelocity, EcsOnAdd, Position); + ECS_SYSTEM(world, OnSetPosition, EcsOnSet, Position); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity = {AddVelocity}, .ctx = (void*)ecs_type(Velocity) + }); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_add(world, e, Position); + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, AddVelocity); + test_int(ctx.column_count, 2); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 2); + test_int(p->y, 3); + + ecs_fini(world); +} + +static +void AddAgain(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Position); + } +} + +void TriggerOnAdd_add_again_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Init, EcsOnAdd, Position); + ECS_SYSTEM(world, AddAgain, EcsOnUpdate, Position); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_add(world, e, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_progress(world, 1); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +static +void AddMass(ecs_iter_t *it) { + ECS_COLUMN(it, Mass, m, 1); + + int i; + for (i = 0; i < it->count; i ++) { + m[i] = 10; + } +} + +void TriggerOnAdd_add_in_progress_before_system_def() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_SYSTEM(world, AddVelocity, EcsOnUpdate, Position, :Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert( ecs_has(world, e, Position)); + test_assert( !ecs_has(world, e, Velocity)); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e, Position)); + test_assert( ecs_has(world, e, Velocity)); + + ECS_COMPONENT(world, Mass); + ECS_TRIGGER(world, AddMass, EcsOnAdd, Mass); + + ecs_add(world, e, Mass); + test_assert( ecs_has(world, e, Mass)); + + const Mass *m = ecs_get(world, e, Mass); + test_assert(m != NULL); + test_int(*m, 10); + + ecs_fini(world); +} + +void SystemA(ecs_iter_t *it) { + int i, tag; + for (i = 0; i < it->count; i ++) { + for (tag = 1000; tag < 1100; tag ++) { + ecs_add_type( + it->world, + it->entities[i], + ecs_type_from_id(it->world, tag)); + } + } +} + +void SystemB(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_has(it->world, it->entities[i], Position); + } +} + +void TriggerOnAdd_2_systems_w_table_creation() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, SystemA, EcsOnAdd, Position); + ECS_TRIGGER(world, SystemB, EcsOnAdd, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_fini(world); +} + +void NewWithPosition(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Position, 1); + + ecs_entity_t e = ecs_new(it->world, Position); + test_assert(e != 0); +} + +void TriggerOnAdd_2_systems_w_table_creation_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, SystemA, EcsOnAdd, Position); + ECS_TRIGGER(world, SystemB, EcsOnAdd, Position); + ECS_SYSTEM(world, NewWithPosition, EcsOnUpdate, :Position); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +static +void TestContext(ecs_iter_t *it) { + void *world_ctx = ecs_get_context(it->world); + test_assert(world_ctx == it->ctx); + int32_t *param = it->ctx; + (*param) ++; +} + +void TriggerOnAdd_sys_context() { + ecs_world_t *world = ecs_init(); + int32_t param = 0; + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, TestContext, EcsOnAdd, Position); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity = {TestContext}, .ctx = ¶m + }); + + test_assert(ecs_get_trigger_ctx(world, TestContext) == ¶m); + + ecs_fini(world); +} + +void TriggerOnAdd_get_sys_context_from_param() { + ecs_world_t *world = ecs_init(); + int32_t param = 0; + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, TestContext, EcsOnAdd, Position); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .entity = {TestContext}, .ctx = ¶m + }); + + /* Set world context so system can compare if pointer is correct */ + ecs_set_context(world, ¶m); + + /* Trigger system */ + ecs_new(world, Position); + + test_int(param, 1); + + ecs_fini(world); +} + +void TriggerOnAdd_remove_added_component_in_on_add_w_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Remove_from_current, EcsOnAdd, Position); + + IterData ctx = {.component = ecs_id(Position)}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Velocity); + ecs_entity_t e2 = ecs_new(world, Velocity); + ecs_entity_t e3 = ecs_new(world, Velocity); + + e1 = ecs_set(world, e1, Position, {10, 20}); + e2 = ecs_set(world, e2, Position, {11, 21}); + e3 = ecs_set(world, e3, Position, {12, 22}); + + test_assert( !ecs_has(world, e1, Position)); + test_assert( !ecs_has(world, e2, Position)); + test_assert( !ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + ecs_fini(world); +} + +void Add_3_to_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + int i; + for (i = 0; i < it->count; i ++) { + if (ctx->component_3) { + ecs_add_id(it->world, it->entities[i], ctx->component_3); + } + ctx->entity_count ++; + } +} + +void TriggerOnAdd_on_add_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TRIGGER(world, Add_to_current, EcsOnAdd, Position); + ECS_TRIGGER(world, Add_3_to_current, EcsOnAdd, Velocity); + + IterData ctx = {.component = ecs_id(Velocity), .component_3 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + test_assert( ecs_has(world, e1, Mass)); + test_assert( ecs_has(world, e2, Mass)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +void TriggerOnAdd_on_remove_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_TRIGGER(world, Remove_from_current, EcsOnAdd, Position); + ECS_TRIGGER(world, Dummy, EcsOnRemove, Velocity); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Type); + ecs_entity_t e2 = ecs_new(world, Type); + ecs_entity_t e3 = ecs_new(world, Type); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( !ecs_has(world, e1, Velocity)); + test_assert( !ecs_has(world, e2, Velocity)); + test_assert( !ecs_has(world, e3, Velocity)); + + test_assert(dummy_called); + + ecs_fini(world); +} + +void TriggerOnAdd_on_set_in_on_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Rotation); + ECS_COMPONENT(world, Mass); + + ECS_TRIGGER(world, Set_current, EcsOnAdd, Position); + ECS_SYSTEM(world, Add_3_to_current, EcsOnSet, Rotation); + + IterData ctx = {.component = ecs_id(Rotation), .component_3 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Rotation)); + test_assert( ecs_has(world, e2, Rotation)); + test_assert( ecs_has(world, e3, Rotation)); + + test_assert( ecs_has(world, e1, Mass)); + test_assert( ecs_has(world, e2, Mass)); + test_assert( ecs_has(world, e3, Mass)); + + const Rotation *r = ecs_get(world, e1, Rotation); + test_assert(r != NULL); + test_int(*r, 10 + e1); + + r = ecs_get(world, e2, Rotation); + test_assert(r != NULL); + test_int(*r, 10 + e2); + + r = ecs_get(world, e3, Rotation); + test_assert(r != NULL); + test_int(*r, 10 + e3); + + ecs_fini(world); +} + +void TriggerOnAdd_on_add_in_on_update() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_SYSTEM(world, Add_to_current, EcsOnUpdate, Position); + ECS_TRIGGER(world, Add_3_to_current, EcsOnAdd, Velocity); + + IterData ctx = {.component = ecs_id(Velocity), .component_3 = ecs_id(Mass)}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Position); + ecs_entity_t e2 = ecs_new(world, Position); + ecs_entity_t e3 = ecs_new(world, Position); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( ecs_has(world, e1, Velocity)); + test_assert( ecs_has(world, e2, Velocity)); + test_assert( ecs_has(world, e3, Velocity)); + + test_assert( ecs_has(world, e1, Mass)); + test_assert( ecs_has(world, e2, Mass)); + test_assert( ecs_has(world, e3, Mass)); + + ecs_fini(world); +} + +void TriggerOnAdd_emplace() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnAdd}, + .callback = Dummy + }); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + test_int(dummy_called, 0); + + Position *p = ecs_emplace(world, e, Position); + test_assert(p != NULL); + test_bool(dummy_called, true); + + ecs_fini(world); +} + +void TriggerOnAdd_add_after_delete_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t trigger = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnAdd}, + .callback = Dummy + }); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_delete(world, trigger); + test_int(dummy_called, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + test_int(dummy_called, 0); + + ecs_fini(world); +} + +void TriggerOnAdd_add_after_delete_wildcard_id_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t trigger = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = EcsWildcard, + .events = {EcsOnAdd}, + .callback = Dummy + }); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_delete(world, trigger); + test_int(dummy_called, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + test_int(dummy_called, 0); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/TriggerOnRemove.c b/fggl/ecs2/flecs/test/api/src/TriggerOnRemove.c new file mode 100644 index 0000000000000000000000000000000000000000..2800846d853e683b2622cddeb89bee8a4d77d234 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/TriggerOnRemove.c @@ -0,0 +1,478 @@ +#include <api.h> + +void Deinit(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + Velocity *v = NULL; + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + } + + probe_system(it); + + /* Write to validate columns point to valid memory */ + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 0; + p[i].y = 0; + + if (v) { + v[i].x = 0; + v[i].y = 0; + } + } +} + +static +void Remove_from_current(ecs_iter_t *it) { + IterData *ctx = ecs_get_context(it->world); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (ctx->component) { + ecs_remove_id(it->world, e, ctx->component); + } + + if (ctx->component_2) { + ecs_remove_id(it->world, e, ctx->component_2); + } + + ctx->entity_count ++; + } +} + +void TriggerOnRemove_remove_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Deinit, EcsOnRemove, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_remove(world, e, Position); + + test_int(ctx.count, 1); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void TriggerOnRemove_remove_match_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Deinit, EcsOnRemove, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_remove(world, e, Type); + + test_int(ctx.count, 1); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void TriggerOnRemove_remove_no_match_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TRIGGER(world, Deinit, EcsOnRemove, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_remove(world, e, Velocity); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnRemove_delete_match_1_of_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TRIGGER(world, Deinit, EcsOnRemove, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_delete(world, e); + + test_int(ctx.count, 1); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void TriggerOnRemove_delete_match_1_of_2() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ECS_TRIGGER(world, Deinit, EcsOnRemove, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Type); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_delete(world, e); + + test_int(ctx.count, 1); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + +void TriggerOnRemove_delete_no_match_1() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TRIGGER(world, Deinit, EcsOnRemove, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Velocity); + test_assert(e != 0); + + test_int(ctx.count, 0); + + ecs_delete(world, e); + + test_int(ctx.count, 0); + + ecs_fini(world); +} + +static Position old_position = {0}; + +static +void RemovePosition(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + test_assert(it->count == 1); + + old_position = p[0]; +} + +void TriggerOnRemove_remove_watched() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, RemovePosition, EcsOnRemove, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + + /* Make parent entity watched */ + ecs_new_w_pair(world, EcsChildOf, e); + + ecs_remove(world, e, Position); + + test_int(old_position.x, 10); + test_int(old_position.y, 20); + + ecs_fini(world); +} + + +void TriggerOnRemove_delete_watched() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TRIGGER(world, RemovePosition, EcsOnRemove, Position); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + test_assert(e != 0); + + /* Make parent entity watched */ + ecs_new_w_pair(world, EcsChildOf, e); + + ecs_delete(world, e); + + test_int(old_position.x, 10); + test_int(old_position.y, 20); + + ecs_fini(world); +} + +static bool dummy_called = false; + +static +void Dummy(ecs_iter_t *it) { + dummy_called = true; +} + +void TriggerOnRemove_on_remove_in_on_update() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ECS_SYSTEM(world, Remove_from_current, EcsOnUpdate, Position); + ECS_TRIGGER(world, Dummy, EcsOnRemove, Velocity); + + IterData ctx = {.component = ecs_id(Velocity)}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_new(world, Type); + ecs_entity_t e2 = ecs_new(world, Type); + ecs_entity_t e3 = ecs_new(world, Type); + + ecs_progress(world, 1); + + test_assert( ecs_has(world, e1, Position)); + test_assert( ecs_has(world, e2, Position)); + test_assert( ecs_has(world, e3, Position)); + + test_assert( !ecs_has(world, e1, Velocity)); + test_assert( !ecs_has(world, e2, Velocity)); + test_assert( !ecs_has(world, e3, Velocity)); + + test_assert(dummy_called); + + ecs_fini(world); +} + +static int dummy_dtor_invoked = 0; + +typedef struct DummyComp { + int value; +} DummyComp; + +static +void RemoveDummyComp(ecs_iter_t *it) { + int i; + for (i = 0; i < it->count; i ++) { + test_assert(ecs_is_valid(it->world, it->entities[i])); + test_assert(ecs_is_alive(it->world, it->entities[i])); + } + + dummy_dtor_invoked ++; +} + +void TriggerOnRemove_valid_entity_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, DummyComp); + ECS_TRIGGER(world, RemoveDummyComp, EcsOnRemove, DummyComp); + + ecs_entity_t e = ecs_new(world, DummyComp); + test_assert(e != 0); + + ecs_delete(world, e); + test_assert(!ecs_is_valid(world, e)); + test_assert(!ecs_is_alive(world, e)); + + test_int(dummy_dtor_invoked, 1); + + ecs_fini(world); +} + +void TriggerOnRemove_remove_after_delete_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t trigger = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnRemove}, + .callback = Dummy + }); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + test_int(dummy_called, 0); + + ecs_remove(world, e1, Position); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_delete(world, trigger); + test_int(dummy_called, 0); + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + test_int(dummy_called, 0); + + ecs_remove(world, e2, Position); + test_int(dummy_called, 0); + + ecs_fini(world); +} + +void TriggerOnRemove_remove_after_delete_wildcard_id_trigger() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t trigger = ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = EcsWildcard, + .events = {EcsOnRemove}, + .callback = Dummy + }); + + ecs_entity_t e1 = ecs_new(world, Position); + test_assert(e1 != 0); + test_assert(ecs_has(world, e1, Position)); + test_int(dummy_called, 0); + + ecs_remove(world, e1, Position); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_delete(world, trigger); + test_int(dummy_called, 1); + + dummy_called = 0; + + ecs_entity_t e2 = ecs_new(world, Position); + test_assert(e2 != 0); + test_assert(ecs_has(world, e2, Position)); + test_int(dummy_called, 0); + + ecs_remove(world, e2, Position); + test_int(dummy_called, 0); + + ecs_fini(world); +} + +typedef struct on_remove_has_tag_t { + ecs_entity_t ent; + ecs_entity_t tag; +} on_remove_has_tag_t; + +static +void OnRemoveHasTag(ecs_iter_t *it) { + on_remove_has_tag_t *ctx = it->ctx; + test_assert(ctx != NULL); + + test_int(it->count, 1); + test_assert(it->entities[0] == ctx->ent); + test_assert(ecs_term_id(it, 1) == ctx->tag); + test_bool(ecs_has_id(it->world, ctx->ent, ctx->tag), true); + + dummy_called = true; +} + +void TriggerOnRemove_has_removed_tag_trigger_1_tag() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_entity_t e = ecs_new(world, Tag); + test_assert(e != 0); + test_assert(ecs_has(world, e, Tag)); + + on_remove_has_tag_t ctx = { + .ent = e, + .tag = Tag + }; + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = Tag, + .events = {EcsOnRemove}, + .callback = OnRemoveHasTag, + .ctx = &ctx + }); + + ecs_remove(world, e, Tag); + + test_int(dummy_called, 1); + + ecs_fini(world); +} + +void TriggerOnRemove_has_removed_tag_trigger_2_tags() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e = ecs_new(world, TagA); + test_assert(e != 0); + test_assert(ecs_has(world, e, TagA)); + + ecs_add(world, e, TagB); + test_assert(ecs_has(world, e, TagB)); + + on_remove_has_tag_t ctx = { + .ent = e, + .tag = TagA + }; + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = TagA, + .events = {EcsOnRemove}, + .callback = OnRemoveHasTag, + .ctx = &ctx + }); + + ecs_remove(world, e, TagA); + + test_int(dummy_called, 1); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/TriggerOnSet.c b/fggl/ecs2/flecs/test/api/src/TriggerOnSet.c new file mode 100644 index 0000000000000000000000000000000000000000..6692d9af6efdec38e1f72fe48b84908f006efa62 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/TriggerOnSet.c @@ -0,0 +1,575 @@ +#include <api.h> + +static +void OnSet(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + Velocity *v = NULL; + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x ++; + + if (v) { + v[i].x = p[i].x; + v[i].y = p[i].y; + } + } +} + +static bool dummy_called = false; + +static +void Dummy(ecs_iter_t *it) { + dummy_called = true; +} + +void TriggerOnSet_set() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.count, 0); + + ecs_set(world, e, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnSet_set_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnSet_set_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Position); + test_int(ctx.count, 0); + + ecs_set(world, e, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 20); + + ecs_set_ptr(world, e, Position, p); + + p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 12); + test_int(p->y, 20); + + ecs_fini(world); +} + +void TriggerOnSet_clone() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 20); + + ctx = (Probe){0}; + + ecs_clone(world, 0, e1, false); + + /* OnSet function should not have been called, because value has not been + * copied */ + test_int(ctx.count, 0); + + ecs_fini(world); +} + +void TriggerOnSet_clone_w_value() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 20); + + ctx = (Probe){0}; + + ecs_entity_t e2 = ecs_clone(world, 0, e1, true); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e2); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 12); + test_int(p->y, 20); + + ecs_fini(world); +} + +static bool add_called; +static bool set_called; + +static +void OnAdd_check_order(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + test_assert(!add_called); + test_assert(!set_called); + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x = 1; + p[i].y = 2; + } + + add_called = true; +} + +static +void OnSet_check_order(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + probe_system(it); + + test_assert(add_called); + test_assert(!set_called); + + int i; + for (i = 0; i < it->count; i ++) { + p[i].x ++; + } + + set_called = true; +} + +void TriggerOnSet_set_and_add_system() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, OnSet_check_order, EcsOnSet, Position); + ECS_TRIGGER(world, OnAdd_check_order, EcsOnAdd, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, 0); + test_int(ctx.count, 0); + + ecs_set(world, e, Position, {10, 20}); + + test_int(ctx.count, 2); + test_int(ctx.invoked, 2); + test_int(ctx.system, OnSet_check_order); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.e[1], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + test_int(ctx.c[1][0], ecs_id(Position)); + test_int(ctx.s[1][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 20); + + test_assert(set_called); + + ecs_fini(world); +} + +static +void OnSetShared(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + Velocity *v = NULL; + if (it->column_count >= 2) { + v = ecs_term(it, Velocity, 2); + } + + probe_system(it); + + int i; + for (i = 0; i < it->count; i ++) { + if (v) { + v[i].x = p->x; + v[i].y = p->y; + } + } +} + +void TriggerOnSet_on_set_after_override() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 3}); + + ECS_SYSTEM(world, OnSetShared, EcsOnSet, ANY:Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + /* instantiate prefab */ + ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, Prefab); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + + test_int(ctx.system, OnSetShared); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], Prefab); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_assert(p == ecs_get(world, Prefab, Position)); + test_int(p->x, 1); + test_int(p->y, 3); + + /* override component (doesn't call system) */ + + ctx = (Probe){0}; + + ecs_add(world, e, Position); + + test_int(ctx.count, 0); + test_int(ctx.invoked, 0); + + const Position *p_after = ecs_get(world, e, Position); + test_assert(p_after != NULL); + test_assert(p != p_after); + test_int(p_after->x, 1); + test_int(p_after->y, 3); + + /* Set component */ + + ecs_set(world, e, Position, {2, 4}); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + + test_int(ctx.system, OnSetShared); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + p_after = ecs_get(world, e, Position); + test_assert(p_after != NULL); + test_assert(p != p_after); + test_int(p_after->x, 2); + test_int(p_after->y, 4); + + ecs_fini(world); +} + +void TriggerOnSet_on_set_after_override_w_new() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 3}); + + ECS_TYPE(world, Type, (IsA, Prefab), Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Type); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + ecs_fini(world); +} + +void TriggerOnSet_on_set_after_override_w_new_w_count() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 3}); + + ECS_TYPE(world, Type, (IsA, Prefab), Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + const ecs_entity_t *ids = ecs_bulk_new(world, Type, 3); + test_assert(ids != NULL); + + test_int(ctx.count, 3); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], ids[0]); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + int i; + for (i = 0; i < 3; i ++) { + const Position *p = ecs_get(world, ids[i], Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + } + + ecs_fini(world); +} + +void TriggerOnSet_on_set_after_override_1_of_2_overridden() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_PREFAB(world, Prefab, Position); + ecs_set(world, Prefab, Position, {1, 3}); + + ECS_TYPE(world, Type, (IsA, Prefab), Position); + + ECS_SYSTEM(world, OnSet, EcsOnSet, Position); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_entity_t e = ecs_new(world, Type); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, OnSet); + test_int(ctx.column_count, 1); + test_null(ctx.param); + + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.s[0][0], 0); + + const Position *p = ecs_get(world, e, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 3); + + ecs_fini(world); +} + +static +void SetPosition(ecs_iter_t *it) { + probe_system(it); +} + +void TriggerOnSet_on_set_after_snapshot_restore() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_SYSTEM(world, SetPosition, EcsOnSet, Position); + + const ecs_entity_t *ids = ecs_bulk_new(world, Position, 10); + test_assert(ids != NULL); + + ecs_entity_t id_arr[10]; + memcpy(id_arr, ids, sizeof(ecs_entity_t) * 10); + + int32_t i; + for (i = 0; i < 10; i ++) { + test_assert(ecs_has(world, ids[i], Position)); + ecs_set(world, ids[i], Position, {i, i * 2}); + } + + Probe ctx = { 0 }; + ecs_set_context(world, &ctx); + + ecs_snapshot_t *s = ecs_snapshot_take(world); + + test_int(ctx.invoked, 0); + + /* Delete one entity, so we have more confidence we're triggering on the + * right entities */ + ecs_delete(world, id_arr[0]); + + test_int(ctx.invoked, 0); + + ecs_snapshot_restore(world, s); + + test_int(ctx.count, 10); + test_int(ctx.invoked, 1); + test_int(ctx.system, SetPosition); + test_int(ctx.column_count, 1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_null(ctx.param); + + for (i = 0; i < 10; i ++) { + test_int(ctx.e[i], id_arr[i]); + } + + ecs_fini(world); +} + +void TriggerOnSet_emplace() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_trigger_init(world, &(ecs_trigger_desc_t){ + .term.id = ecs_id(Position), + .events = {EcsOnSet}, + .callback = Dummy + }); + + ecs_entity_t e = ecs_new_id(world); + test_assert(e != 0); + test_int(dummy_called, 0); + + Position *p = ecs_emplace(world, e, Position); + test_assert(p != NULL); + test_int(dummy_called, 0); + + ecs_modified(world, e, Position); + test_bool(dummy_called, true); + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/Type.c b/fggl/ecs2/flecs/test/api/src/Type.c new file mode 100644 index 0000000000000000000000000000000000000000..b9b3942af3303705b216dcad236c40262847b7d3 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/Type.c @@ -0,0 +1,1092 @@ +#include <api.h> +#include <flecs/type.h> + +void Type_setup() { + ecs_tracing_enable(-2); +} + +void Type_type_of_1_tostr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TYPE(world, Type, Position); + + char *str = ecs_type_str(world, ecs_type(Type)); + + test_str(str, "Position"); + + free(str); + + ecs_fini(world); +} + +void Type_type_of_2_tostr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TYPE(world, Type, Position, Velocity); + + char *str = ecs_type_str(world, ecs_type(Type)); + + test_str(str, "Position,Velocity"); + + free(str); + + ecs_fini(world); +} + +void Type_type_of_2_tostr_no_id() { + ecs_world_t *world = ecs_init(); + + ecs_type_t t_1 = ecs_type_from_id(world, 100); + ecs_type_t t_2 = ecs_type_from_id(world, 200); + ecs_type_t t = 0; + t = ecs_type_merge(world, t, t_1, 0); + t = ecs_type_merge(world, t, t_2, 0); + + char *str = ecs_type_str(world, t); + + test_str(str, "100,200"); + + free(str); + + ecs_fini(world); +} + +void Type_type_redefine() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t type = ecs_type_init(world, &(ecs_type_desc_t){ + .entity.name = "Type", + .ids_expr = "Position" + }); + + ecs_entity_t type_2 = ecs_type_init(world, &(ecs_type_desc_t){ + .entity.name = "Type", + .ids_expr = "Position" + }); + + test_assert(type == type_2); + + ecs_fini(world); +} + +void Type_type_has() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TYPE(world, Type, Position, Velocity); + + test_assert( ecs_type_has_entity(world, ecs_type(Type), ecs_id(Position))); + test_assert( ecs_type_has_entity(world, ecs_type(Type), ecs_id(Velocity))); + + ecs_fini(world); +} + +void Type_type_has_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_TYPE(world, Type, Position); + + test_assert( ecs_type_has_entity(world, ecs_type(Type), ecs_id(Position))); + test_assert( !ecs_type_has_entity(world, ecs_type(Type), ecs_id(Velocity))); + + ecs_fini(world); +} + +void Type_zero_type_has_not() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + test_assert( !ecs_type_has_entity(world, 0, ecs_id(Position))); + + ecs_fini(world); +} + +void Type_type_merge() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type1, Position, Velocity); + ECS_TYPE(world, Type2, Mass); + + ecs_type_t t = ecs_type_merge(world, ecs_type(Type1), ecs_type(Type2), 0); + test_assert(t != NULL); + test_int( ecs_vector_count(t), 3); + + ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t); + test_int(entities[0], ecs_id(Position)); + test_int(entities[1], ecs_id(Velocity)); + test_int(entities[2], ecs_id(Mass)); + + ecs_fini(world); +} + +void Type_type_merge_overlap() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type1, Position, Velocity); + ECS_TYPE(world, Type2, Velocity, Mass); + + ecs_type_t t = ecs_type_merge(world, ecs_type(Type1), ecs_type(Type2), 0); + test_assert(t != NULL); + test_int( ecs_vector_count(t), 3); + + ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t); + test_int(entities[0], ecs_id(Position)); + test_int(entities[1], ecs_id(Velocity)); + test_int(entities[2], ecs_id(Mass)); + + ecs_fini(world); +} + +void Type_type_merge_overlap_one() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ECS_TYPE(world, Type1, Position, Velocity); + ECS_TYPE(world, Type2, Position); + + ecs_type_t t = ecs_type_merge(world, ecs_type(Type1), ecs_type(Type2), 0); + test_assert(t != NULL); + test_int( ecs_vector_count(t), 2); + + ecs_entity_t *entities = ecs_vector_first(t, ecs_entity_t); + test_int(entities[0], ecs_id(Position)); + test_int(entities[1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type != ecs_type(Type)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 2); + test_int(type_array[0], ecs_id(Position)); + test_int(type_array[1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_type_t new_type = ecs_type_add(world, NULL, ecs_id(Velocity)); + test_assert(new_type != NULL); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 1); + test_int(type_array[0], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add_entity_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type == ecs_type(Type)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 2); + test_int(type_array[0], ecs_id(Position)); + test_int(type_array[1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add_out_of_order() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Velocity); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type), ecs_id(Position)); + test_assert(new_type != NULL); + test_assert(new_type != ecs_type(Type)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 2); + test_int(type_array[0], ecs_id(Position)); + test_int(type_array[1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type1, Position, Velocity); + ECS_TYPE(world, Type2, Position); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type2), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type != ecs_type(Type2)); + test_assert(new_type == ecs_type(Type1)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 2); + test_int(type_array[0], ecs_id(Position)); + test_int(type_array[1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add_empty_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type1, Velocity); + + ecs_type_t new_type = ecs_type_add(world, NULL, ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type == ecs_type(Type1)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 1); + test_int(type_array[0], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_add_out_of_order_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type1, Velocity); + ECS_TYPE(world, Type2, Position, Velocity); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type1), ecs_id(Position)); + test_assert(new_type != NULL); + test_assert(new_type != ecs_type(Type1)); + test_assert(new_type == ecs_type(Type2)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 2); + test_int(type_array[0], ecs_id(Position)); + test_int(type_array[1], ecs_id(Velocity)); + + ecs_fini(world); +} + +void Type_type_of_2_add() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Position, Mass); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type != ecs_type(Type)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 3); + test_int(type_array[0], ecs_id(Velocity)); + test_int(type_array[1], ecs_id(Position)); + test_int(type_array[2], ecs_id(Mass)); + + ecs_fini(world); +} + +void Type_type_of_3_add_entity_again() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Mass); + ECS_TYPE(world, Type, Velocity, Position, Mass); + + ecs_type_t new_type = ecs_type_add(world, ecs_type(Type), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type == ecs_type(Type)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 3); + test_int(type_array[0], ecs_id(Velocity)); + test_int(type_array[1], ecs_id(Position)); + test_int(type_array[2], ecs_id(Mass)); + + ecs_fini(world); +} + +void Type_invalid_container_type_expression() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_TYPE(world, Type, PARENT:Position, Velocity); + + ecs_fini(world); +} + +void Type_invalid_entity_type_expression() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, Entity, Position); + + test_expect_abort(); + + ECS_TYPE(world, Type, Entity:Position, Velocity); + + ecs_fini(world); +} + +void Type_invalid_system_type_expression() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Position); + + test_expect_abort(); + + ECS_TYPE(world, Type, SYSTEM:Position, Velocity); + + ecs_fini(world); +} + +void Type_type_from_empty_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + + ecs_type_t t = ecs_type_from_id(world, e); + test_assert(t != NULL); + + test_int(ecs_vector_count(t), 1); + + ecs_entity_t *array = ecs_vector_first(t, ecs_entity_t); + test_int(array[0], e); + + ecs_fini(world); +} + +void Type_get_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + + ecs_type_t t = ecs_get_type(world, e); + test_assert(t != NULL); + test_int(ecs_vector_count(t), 1); + + ecs_entity_t *type_array = ecs_vector_first(t, ecs_entity_t); + test_assert(type_array != NULL); + test_int(type_array[0], ecs_id(Position)); + + ecs_fini(world); +} + +void Type_get_type_from_empty() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + + ecs_type_t t = ecs_get_type(world, e); + test_assert(t == NULL); + + ecs_fini(world); +} + +void Type_get_type_from_0() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + ecs_get_type(world, 0); +} + +void Type_entity_from_type() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_TYPE(world, MyType, Position); + + ecs_entity_t e = ecs_type_to_entity(world, ecs_type(MyType)); + test_assert(e != 0); + test_assert(e == ecs_id(Position)); + + ecs_fini(world); +} + +void Type_entity_from_empty_type() { + ecs_world_t *world = ecs_init(); + + ECS_TYPE(world, MyType, 0); + + ecs_entity_t e = ecs_type_to_entity(world, ecs_type(MyType)); + test_assert(e == 0); + + ecs_fini(world); +} + +void Type_entity_from_type_w_2_elements() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, MyType, Position, Velocity); + + test_expect_abort(); + + ecs_type_to_entity(world, ecs_type(MyType)); + + ecs_fini(world); +} + +void Type_type_from_entity() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_ENTITY(world, Entity, Position); + + ecs_type_t t = ecs_type_from_id(world, Entity); + test_assert(t != NULL); + test_int(ecs_vector_count(t), 1); + + ecs_entity_t *type_array = ecs_vector_first(t, ecs_entity_t); + test_int(type_array[0], Entity); + + ecs_fini(world); +} + +void Type_type_from_empty() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Entity, 0); + + ecs_type_t t = ecs_type_from_id(world, Entity); + test_assert(t != NULL); + test_int(ecs_vector_count(t), 1); + + ecs_entity_t *type_array = ecs_vector_first(t, ecs_entity_t); + test_int(type_array[0], Entity); + + ecs_fini(world); +} + +void Type_type_from_0() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + ecs_type_from_id(world, 0); +} + +void Type_type_remove() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + ecs_type_t new_type = ecs_type_remove(world, ecs_type(Type), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type != ecs_type(Type)); + + ecs_entity_t *type_array = ecs_vector_first(new_type, ecs_entity_t); + test_assert(type_array != NULL); + test_int(ecs_vector_count(new_type), 1); + test_int(type_array[0], ecs_id(Position)); + + ecs_fini(world); +} + +void Type_type_remove_empty() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TYPE(world, Type, 0); + + ecs_type_t new_type = ecs_type_remove(world, ecs_type(Type), ecs_id(Position)); + test_assert(new_type == NULL); + + ecs_fini(world); +} + +void Type_type_remove_non_existing() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position); + + ecs_type_t new_type = ecs_type_remove(world, ecs_type(Type), ecs_id(Velocity)); + test_assert(new_type != NULL); + test_assert(new_type == ecs_type(Type)); + + ecs_fini(world); +} + +void Type_type_to_expr_1_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position); + + char *expr = ecs_type_str(world, ecs_type(Type)); + test_str(expr, "Position"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void Type_type_to_expr_2_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + + char *expr = ecs_type_str(world, ecs_type(Type)); + test_str(expr, "Position,Velocity"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void Type_type_to_expr_instanceof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, (IsA, Position)); + + char *expr = ecs_type_str(world, ecs_type(Type)); + test_str(expr, "(IsA,Position)"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void Type_type_to_expr_childof() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, (ChildOf, Position)); + + char *expr = ecs_type_str(world, ecs_type(Type)); + test_str(expr, "(ChildOf,Position)"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void Type_type_to_expr_pair() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + test_expect_abort(); + + /* Cannot create a type that just sets the hi id */ + ECS_TYPE(world, Type, (Position, *)); +} + +void Type_type_to_expr_pair_w_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, (Position, Velocity)); + + char *expr = ecs_type_str(world, ecs_type(Type)); + test_str(expr, "(Position,Velocity)"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void Type_type_to_expr_scope() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, scope, 0); + ecs_set_scope(world, scope); + ECS_COMPONENT(world, Velocity); + ECS_TYPE(world, Type, Position, Velocity); + ecs_set_scope(world, 0); + + char *expr = ecs_type_str(world, ecs_type(Type)); + test_str(expr, "Position,scope.Velocity"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void Type_type_from_expr() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ecs_type_t type = ecs_type_from_str(world, "Position, Velocity"); + test_int(ecs_vector_count(type), 2); + test_assert(ecs_type_has_entity(world, type, ecs_id(Position))); + test_assert(ecs_type_has_entity(world, type, ecs_id(Velocity))); + + ecs_fini(world); +} + +void Type_type_from_expr_scope() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_ENTITY(world, scope, 0); + ecs_set_scope(world, scope); + ECS_COMPONENT(world, Velocity); + ecs_set_scope(world, 0); + + ecs_type_t type = ecs_type_from_str(world, "Position, scope.Velocity"); + test_int(ecs_vector_count(type), 2); + test_assert(ecs_type_has_entity(world, type, ecs_id(Position))); + test_assert(ecs_type_has_entity(world, type, ecs_id(Velocity))); + + ecs_fini(world); +} + +void Type_type_from_expr_digit() { + ecs_world_t *world = ecs_init(); + + ecs_type_t type = ecs_type_from_str(world, "10, 20"); + test_int(ecs_vector_count(type), 2); + test_assert(ecs_type_has_entity(world, type, 10)); + test_assert(ecs_type_has_entity(world, type, 20)); + + ecs_fini(world); +} + +void Type_type_from_expr_instanceof() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Base, 0); + + ecs_type_t type = ecs_type_from_str(world, "(IsA, Base)"); + test_int(ecs_vector_count(type), 1); + test_assert(ecs_type_has_entity(world, type, ecs_pair(EcsIsA, Base))); + + ecs_fini(world); +} + +void Type_type_from_expr_childof() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Parent, 0); + + ecs_type_t type = ecs_type_from_str(world, "(ChildOf, Parent)"); + test_int(ecs_vector_count(type), 1); + test_assert(ecs_type_has_entity(world, type, ecs_pair(EcsChildOf, Parent))); + + ecs_fini(world); +} + +void Type_type_from_expr_pair() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Pair); + + /* Legacy notation, translates to (Pair, *) */ + ecs_type_t type = ecs_type_from_str(world, "(Pair, *)"); + + test_int(ecs_vector_count(type), 1); + test_assert(ecs_type_has_entity(world, type, ecs_pair(Pair, EcsWildcard))); + + ecs_fini(world); +} + +void Type_type_from_expr_pair_w_comp() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_TAG(world, Pair); + + ecs_type_t type = ecs_type_from_str(world, "(Pair, Position)"); + test_int(ecs_vector_count(type), 1); + test_assert(ecs_type_has_entity(world, type, ecs_pair(Pair, ecs_id(Position)))); + + ecs_fini(world); +} + +void Type_entity_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, e, 0); + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "e"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_path_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, parent, 0); + ECS_ENTITY(world, e, (ChildOf, parent)); + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "parent.e"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_instanceof_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ecs_pair(EcsIsA, Foo); + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "(IsA,Foo)"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_childof_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ecs_pair(EcsChildOf, Foo); + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "(ChildOf,Foo)"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_pair_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + ECS_ENTITY(world, Bar, 0); + + ecs_entity_t e = ecs_pair(Bar, Foo); + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "(Bar,Foo)"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_switch_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ECS_SWITCH | Foo; + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "SWITCH|Foo"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_case_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ECS_CASE | Foo; + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "CASE|Foo"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_and_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ECS_AND | Foo; + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "AND|Foo"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_or_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ECS_OR | Foo; + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "OR|Foo"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_xor_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ECS_XOR | Foo; + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "XOR|Foo"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_not_str() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ECS_NOT | Foo; + + char buffer[256]; + size_t result = ecs_id_str(world, e, buffer, 256); + test_str(buffer, "NOT|Foo"); + test_int(strlen(buffer), result); + + ecs_fini(world); +} + +void Type_entity_str_small_buffer() { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, Foo, 0); + + ecs_entity_t e = ecs_pair(EcsChildOf, Foo); + + char buffer[10]; + size_t result = ecs_id_str(world, e, buffer, 10); + test_str(buffer, "(ChildOf,"); + test_int(strlen(buffer), 9); + test_int(strlen("(ChildOf,Foo)"), result); + + ecs_fini(world); +} + +void Type_role_pair_str() { + ecs_entity_t e = ECS_PAIR; + test_str(ecs_role_str(e), "PAIR"); +} + +void Type_role_switch_str() { + ecs_entity_t e = ECS_SWITCH; + test_str(ecs_role_str(e), "SWITCH"); +} + +void Type_role_case_str() { + ecs_entity_t e = ECS_CASE; + test_str(ecs_role_str(e), "CASE"); +} + +void Type_role_and_str() { + ecs_entity_t e = ECS_AND; + test_str(ecs_role_str(e), "AND"); +} + +void Type_role_or_str() { + ecs_entity_t e = ECS_OR; + test_str(ecs_role_str(e), "OR"); +} + +void Type_role_xor_str() { + ecs_entity_t e = ECS_XOR; + test_str(ecs_role_str(e), "XOR"); +} + +void Type_role_not_str() { + ecs_entity_t e = ECS_NOT; + test_str(ecs_role_str(e), "NOT"); +} + +void Type_role_owned_str() { + ecs_entity_t e = ECS_OWNED; + test_str(ecs_role_str(e), "OWNED"); +} + +void Type_role_disabled_str() { + ecs_entity_t e = ECS_DISABLED; + test_str(ecs_role_str(e), "DISABLED"); +} + +void Type_large_type_expr() { + ecs_world_t *world = ecs_init(); + + int i; + for (i = 0; i < 64; i ++) { + char buff[4] = { 'e' }; + sprintf(&buff[1], "%d", i + 1); + ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = buff + }); + } + + ecs_entity_t type_ent = ecs_type_init(world, &(ecs_type_desc_t) { + .ids_expr = "e1, e2, e3, e4, e5, e6, e7, e8, e9, e10," + "e11, e12, e13, e14, e15, e16, e17, e18, e19, e20," + "e21, e22, e23, e24, e25, e26, e27, e28, e29, e30," + "e31, e32, e33, e34, e35, e36, e37, e38, e39, e40," + "e41, e42, e43, e44, e45, e46, e47, e48, e49, e50," + "e51, e52, e53, e54, e55, e56, e57, e58, e59, e60," + "e61, e62, e63, e64" + }); + + test_assert(type_ent != 0); + + const EcsType *ptr = ecs_get(world, type_ent, EcsType); + test_assert(ptr != NULL); + test_assert(ecs_vector_count(ptr->type) == 64); + test_assert(ecs_vector_count(ptr->normalized) == 64); + + for (i = 0; i < 64; i ++) { + char buff[4] = { 'e' }; + sprintf(&buff[1], "%d", i + 1); + ecs_entity_t e = ecs_lookup(world, buff); + test_assert(e != 0); + test_str(ecs_get_name(world, e), buff); + + test_assert(ecs_type_index_of(ptr->type, 0, e) == i); + } + + ecs_fini(world); +} + +void Type_large_type_expr_limit() { + ecs_world_t *world = ecs_init(); + + test_assert(ECS_MAX_ADD_REMOVE == 32); + + int i; + for (i = 0; i < 32; i ++) { + char buff[4] = { 'e' }; + sprintf(&buff[1], "%d", i + 1); + ecs_entity_init(world, &(ecs_entity_desc_t) { + .name = buff + }); + } + + ecs_entity_t type_ent = ecs_type_init(world, &(ecs_type_desc_t) { + .ids_expr = "e1, e2, e3, e4, e5, e6, e7, e8, e9, e10," + "e11, e12, e13, e14, e15, e16, e17, e18, e19, e20," + "e21, e22, e23, e24, e25, e26, e27, e28, e29, e30," + "e31, e32" + }); + + test_assert(type_ent != 0); + + const EcsType *ptr = ecs_get(world, type_ent, EcsType); + test_assert(ptr != NULL); + test_assert(ecs_vector_count(ptr->type) == 32); + test_assert(ecs_vector_count(ptr->normalized) == 32); + + for (i = 0; i < 32; i ++) { + char buff[4] = { 'e' }; + sprintf(&buff[1], "%d", i + 1); + ecs_entity_t e = ecs_lookup(world, buff); + test_assert(e != 0); + test_str(ecs_get_name(world, e), buff); + + test_assert(ecs_type_index_of(ptr->type, 0, e) == i); + } + + ecs_fini(world); +} diff --git a/fggl/ecs2/flecs/test/api/src/World.c b/fggl/ecs2/flecs/test/api/src/World.c new file mode 100644 index 0000000000000000000000000000000000000000..65c3599b1c4cf359740adefafea8002e570b4c29 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/World.c @@ -0,0 +1,1107 @@ +#include <api.h> + +void World_setup() { + ecs_tracing_enable(-3); +} + +static +void Move(ecs_iter_t *it) { + Position *pos = ecs_term(it, Position, 1); + Velocity *vel = ecs_term(it, Velocity, 2); + probe_system(it); + + int row; + for (row = 0; row < it->count; row ++) { + Position *p = &pos[row]; + Velocity *v = &vel[row]; + p->x += v->x * it->delta_time; + p->y += v->y * it->delta_time; + } +} + +void World_progress_w_0() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position, Velocity); + + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_set(world, e1, Position, {0, 0}); + ecs_set(world, e1, Velocity, {1, 2}); + + ecs_progress(world, 0); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Move); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][0], 0); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_assert(p->x != 0); + test_assert(p->y != 0); + + ecs_fini(world); +} + +void World_progress_w_t() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_ENTITY(world, e1, Position, Velocity); + + ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + + Probe ctx = {0}; + ecs_set_context(world, &ctx); + + ecs_set(world, e1, Position, {0, 0}); + ecs_set(world, e1, Velocity, {1, 2}); + + ecs_progress(world, 2); + + test_int(ctx.count, 1); + test_int(ctx.invoked, 1); + test_int(ctx.system, Move); + test_int(ctx.column_count, 2); + test_null(ctx.param); + + test_int(ctx.e[0], e1); + test_int(ctx.c[0][0], ecs_id(Position)); + test_int(ctx.c[0][1], ecs_id(Velocity)); + test_int(ctx.s[0][0], 0); + test_int(ctx.s[0][1], 0); + + const Position *p = ecs_get(world, e1, Position); + test_assert(p != NULL); + test_int(p->x, 2); + test_int(p->y, 4); + + ecs_fini(world); +} + +void World_entity_range_offset() { + ecs_world_t *world = ecs_init(); + + ecs_set_entity_range(world, 5000, 0); + + ecs_entity_t e = ecs_new(world, 0); + test_int(e, 5000); + + ecs_fini(world); +} + +void World_entity_range_offset_out_of_range() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_enable_range_check(world, true); + ecs_set_entity_range(world, 2000, 0); + + test_expect_abort(); + + ecs_add(world, 1500, Position); + + ecs_fini(world); +} + +void World_entity_range_limit_out_of_range() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_enable_range_check(world, true); + ecs_set_entity_range(world, 0, 2000); + + test_expect_abort(); + + ecs_add(world, 2500, Position); + + ecs_fini(world); +} + +void World_entity_range_out_of_range_check_disabled() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_enable_range_check(world, false); + ecs_set_entity_range(world, 5000, 10000); + + /* Validate that range is being used when issuing new ids */ + ecs_entity_t e = ecs_new(world, 0); + test_int(e, 5000); + + /* Validate that application does not abort when changing out of range */ + ecs_entity_t e2 = ecs_set(world, 4999, Position, {10, 20}); + test_int(e2, 4999); + test_assert( ecs_has(world, e2, Position)); + + const Position *p = ecs_get(world, e2, Position); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + ecs_fini(world); +} + +void World_entity_range_check_after_delete() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_enable_range_check(world, true); + ecs_set_entity_range(world, 5000, 10000); + + ecs_entity_t e = ecs_new(world, 0); + test_assert(e != 0); + test_assert(e == 5000); + + ecs_delete(world, e); + + e = ecs_new(world, 0); + test_assert(e != 0); + test_assert((uint32_t)e == 5000); + + ecs_fini(world); +} + + +void AddToExisting(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_add(it->world, it->entities[i], Velocity); + } +} + +void World_entity_range_add_existing_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddToExisting, EcsOnUpdate, Position, :Velocity); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + test_assert(e < 500); + + ecs_set_entity_range(world, 500, 1000); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +void World_entity_range_add_in_range_in_progress() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddToExisting, EcsOnUpdate, Position, :Velocity); + + ecs_set_entity_range(world, 500, 1000); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e == 500); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +void AddOutOfRange(ecs_iter_t *it) { + ECS_COLUMN_COMPONENT(it, Velocity, 2); + + int i; + for (i = 0; i < it->count; i ++) { + test_expect_abort(); + ecs_add(it->world, 1001, Velocity); + } +} + +void World_entity_range_add_out_of_range_in_progress() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + + ECS_SYSTEM(world, AddOutOfRange, EcsOnUpdate, Position, :Velocity); + + ecs_enable_range_check(world, true); + ecs_set_entity_range(world, 500, 1000); + + /* Dummy entity to invoke the system */ + ecs_entity_t e = ecs_new(world, Position); + test_assert(e == 500); + + ecs_progress(world, 1); + + ecs_fini(world); +} + +void World_get_tick() { + ecs_world_t *world = ecs_init(); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + test_int(stats->frame_count_total, 0); + + ecs_progress(world, 1); + + test_int(stats->frame_count_total, 1); + + ecs_progress(world, 1); + + test_int(stats->frame_count_total, 2); + + ecs_fini(world); +} + +static int32_t malloc_count; + +static +void *test_malloc(ecs_size_t size) { + malloc_count ++; + return malloc(size); +} + +static +void *test_calloc(ecs_size_t size) { + malloc_count ++; + return calloc(size, 1); +} + +static +void *test_realloc(void *old_ptr, ecs_size_t size) { + malloc_count ++; + return realloc(old_ptr, size); +} + +void World_dim() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.malloc_ = test_malloc; + os_api.calloc_ = test_calloc; + os_api.realloc_ = test_realloc; + ecs_os_set_api(&os_api); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + /* Create single entity so that the table exists. This makes the allocation + * counts more predictable, as new_w_count won't trigger table creation */ + ecs_new(world, Position); + + ecs_dim(world, 1100); + + malloc_count = 0; + + ecs_bulk_new(world, Position, 500); + + test_int(malloc_count, 3); + + malloc_count = 0; + + ecs_bulk_new(world, Position, 500); + + test_int(malloc_count, 3); + + ecs_fini(world); +} + +void World_dim_dim_type() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.malloc_ = test_malloc; + os_api.calloc_ = test_calloc; + os_api.realloc_ = test_realloc; + ecs_os_set_api(&os_api); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_dim(world, 1000); + ecs_dim_type(world, ecs_type(Position), 1000); + + malloc_count = 0; + + ecs_bulk_new(world, Position, 500); + + test_int(malloc_count, 0); + + malloc_count = 0; + + ecs_bulk_new(world, Position, 400); + + test_int(malloc_count, 0); + + ecs_fini(world); +} + +static +void TOnLoad(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 0); + p[i].x ++; + } +} + +static +void TPostLoad(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 1); + p[i].x ++; + } +} + +static +void TPreUpdate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 2); + p[i].x ++; + } +} + +static +void TOnUpdate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 3); + p[i].x ++; + } +} + +static +void TOnValidate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 4); + p[i].x ++; + } +} + +static +void TPostUpdate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 5); + p[i].x ++; + } +} + +static +void TPreStore(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 6); + p[i].x ++; + } +} + +static +void TOnStore(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 7); + p[i].x ++; + } +} + +static +void TManual(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 8); + p[i].x ++; + } +} + +void World_phases() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TOnLoad, EcsOnLoad, Position); + ECS_SYSTEM(world, TPostLoad, EcsPostLoad, Position); + ECS_SYSTEM(world, TPreUpdate, EcsPreUpdate, Position); + ECS_SYSTEM(world, TOnUpdate, EcsOnUpdate, Position); + ECS_SYSTEM(world, TOnValidate, EcsOnValidate, Position); + ECS_SYSTEM(world, TPostUpdate, EcsPostUpdate, Position); + ECS_SYSTEM(world, TPreStore, EcsPreStore, Position); + ECS_SYSTEM(world, TOnStore, EcsOnStore, Position); + ECS_SYSTEM(world, TManual, 0, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_set(world, e, Position, {0, 0}); + + ecs_progress(world, 1); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 8); + + ecs_run(world, TManual, 0, NULL); + + test_int(p->x, 9); + + ecs_fini(world); +} + +void World_phases_match_in_create() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_set(world, e, Position, {0, 0}); + + ECS_SYSTEM(world, TOnLoad, EcsOnLoad, Position); + ECS_SYSTEM(world, TPostLoad, EcsPostLoad, Position); + ECS_SYSTEM(world, TPreUpdate, EcsPreUpdate, Position); + ECS_SYSTEM(world, TOnUpdate, EcsOnUpdate, Position); + ECS_SYSTEM(world, TOnValidate, EcsOnValidate, Position); + ECS_SYSTEM(world, TPostUpdate, EcsPostUpdate, Position); + ECS_SYSTEM(world, TPreStore, EcsPreStore, Position); + ECS_SYSTEM(world, TOnStore, EcsOnStore, Position); + ECS_SYSTEM(world, TManual, 0, Position); + + ecs_progress(world, 1); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 8); + + ecs_run(world, TManual, 0, NULL); + + test_int(p->x, 9); + + ecs_fini(world); +} + +static +void TMergeOnLoad(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 0); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergePostLoad(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 1); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergePreUpdate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 2); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergeOnUpdate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 3); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergeOnValidate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 4); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergePostUpdate(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 5); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergePreStore(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 6); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergeOnStore(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 7); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +static +void TMergeManual(ecs_iter_t *it) { + ECS_COLUMN(it, Position, p, 1); + + int i; + for (i = 0; i < it->count; i ++) { + test_int(p[i].x, 8); + ecs_set(it->world, it->entities[i], Position, {p[i].x + 1, 0}); + } +} + +void World_phases_w_merging() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TMergeOnLoad, EcsOnLoad, Position, [out] :Position); + ECS_SYSTEM(world, TMergePostLoad, EcsPostLoad, Position, [out] :Position); + ECS_SYSTEM(world, TMergePreUpdate, EcsPreUpdate, Position, [out] :Position); + ECS_SYSTEM(world, TMergeOnUpdate, EcsOnUpdate, Position, [out] :Position); + ECS_SYSTEM(world, TMergeOnValidate, EcsOnValidate, Position, [out] :Position); + ECS_SYSTEM(world, TMergePostUpdate, EcsPostUpdate, Position, [out] :Position); + ECS_SYSTEM(world, TMergePreStore, EcsPreStore, Position, [out] :Position); + ECS_SYSTEM(world, TMergeOnStore, EcsOnStore, Position, [out] :Position); + ECS_SYSTEM(world, TMergeManual, 0, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + ecs_set(world, e, Position, {0, 0}); + + ecs_progress(world, 1); + + const Position *p = ecs_get(world, e, Position); + test_int(p->x, 8); + + ecs_run(world, TMergeManual, 0, NULL); + + p = ecs_get(world, e, Position); + test_int(p->x, 9); + + ecs_fini(world); +} + +static +void TimeCheck(ecs_iter_t *it) { + test_assert(it->delta_time > 0); +} + +void World_measure_time() { + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TimeCheck, EcsOnLoad, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + int i = 0; + for (i = 0; i < 1000; i ++) { + ecs_progress(world, 0); + } + + ecs_fini(world); +} + +void World_control_fps() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + ECS_COMPONENT(world, Position); + + ECS_SYSTEM(world, TimeCheck, EcsOnLoad, Position); + + ecs_entity_t e = ecs_new(world, Position); + test_assert(e != 0); + + double start, now = 0; + ecs_set_target_fps(world, 20); + + /* Run a few times to give the code an opportunity to calibrate */ + ecs_progress(world, 0); + ecs_progress(world, 0); + ecs_progress(world, 0); + ecs_progress(world, 0); + ecs_progress(world, 0); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Run for one second */ + int count = 0; + do { + ecs_progress(world, 0); + if (!count) { + start = stats->delta_time; + } + + now += stats->delta_time; + count ++; + } while ((now - start) < 1.0); + + /* CI can be unpredictable, just make sure it's in the right ballpark */ + test_assert(count >= 15); + test_assert(count < 25); + + ecs_fini(world); +} + +static +void busy_wait(float wait_time) { + ecs_time_t start, t; + ecs_os_get_time(&start); + + do { + t = start; + } while (ecs_time_measure(&t) < wait_time); +} + +static +void BusySystem(ecs_iter_t *it) { + /* Spend 14msec doing something */ + busy_wait(0.014); +} + +void World_control_fps_busy_system() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, BusySystem, EcsOnUpdate, 0); + + double start, now = 0; + ecs_set_target_fps(world, 20); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Run for one second */ + int count = 0; + do { + ecs_progress(world, 0); + if (!count) { + start = stats->delta_time; + } + + now += stats->delta_time; + count ++; + } while ((now - start) < 1.0); + + /* FPS control relies on sleep, which relies on the OS scheduler. Therefore + * pick a wide enough range to avoid tests failing at random. */ + test_assert(count >= 15); + test_assert(count < 25); + + ecs_fini(world); +} + +void World_control_fps_busy_app() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + double start, now = 0; + ecs_set_target_fps(world, 20); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Run for one second */ + int count = 0; + do { + ecs_progress(world, 0); + if (!count) { + start = stats->delta_time; + } + + now += stats->delta_time; + count ++; + + busy_wait(0.014); + } while ((now - start) < 1.0); + + /* FPS control relies on sleep, which relies on the OS scheduler. Therefore + * pick a wide enough range to avoid tests failing at random. */ + test_assert(count >= 15); + test_assert(count < 25); + + ecs_fini(world); +} + +void World_measure_fps_vs_actual() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + ecs_set_target_fps(world, 60); + + /* Run 10 times, test if one second has passed */ + ecs_time_t t; + ecs_os_get_time(&t); + int32_t i; + for (i = 0; i < 60; i ++) { + ecs_progress(world, 0); + } + + float elapsed = ecs_time_measure(&t); + test_assert(elapsed >= 0.9); + + ecs_fini(world); +} + +void World_measure_delta_time_vs_actual() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + ecs_set_target_fps(world, 60); + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Run 10 times, test if one second has passed */ + ecs_time_t t; + float delta_time = 0; + ecs_os_get_time(&t); + int32_t i; + for (i = 0; i < 60; i ++) { + ecs_progress(world, 0); + delta_time += stats->delta_time; + } + + float elapsed = ecs_time_measure(&t); + test_assert(delta_time - elapsed < 0.1); + test_assert(elapsed >= 0.9); + + ecs_fini(world); +} + +static +void RandomSystem(ecs_iter_t *it) { + /* wait at most 16msec */ + float rnd_time = ((float)rand() / (float)RAND_MAX) * 0.016; + busy_wait(rnd_time); +} + +void World_control_fps_random_system() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + ECS_SYSTEM(world, RandomSystem, EcsOnUpdate, 0); + + double start, now = 0; + ecs_set_target_fps(world, 20); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Run for one second */ + int count = 0; + do { + ecs_progress(world, 0); + if (!count) { + start = stats->delta_time; + } + + now += stats->delta_time; + count ++; + } while ((now - start) < 1.0); + + /* FPS control relies on sleep, which relies on the OS scheduler. Therefore + * pick a wide enough range to avoid tests failing at random. */ + test_assert(count >= 15); + test_assert(count < 25); + + ecs_fini(world); +} + +void World_control_fps_random_app() { + test_is_flaky(); + + ecs_world_t *world = ecs_init(); + + double start, now = 0; + ecs_set_target_fps(world, 20); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + /* Run for one second */ + int count = 0; + do { + ecs_progress(world, 0); + if (!count) { + start = stats->delta_time; + } + + now += stats->delta_time; + count ++; + + float rnd_time = ((float)rand() / (float)RAND_MAX) * 0.016; + busy_wait(rnd_time); + } while ((now - start) < 1.0); + + /* FPS control relies on sleep, which relies on the OS scheduler. Therefore + * pick a wide enough range to avoid tests failing at random. */ + test_assert(count >= 15); + test_assert(count < 25); + + ecs_fini(world); +} + +void World_quit() { + ecs_world_t *world = ecs_init(); + + int32_t count = 0; + + while (ecs_progress(world, 0)) { + test_int(count, 0); + ecs_quit(world); + count ++; + } + + ecs_fini(world); +} + +void World_get_delta_time() { + ecs_world_t *world = ecs_init(); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + test_int(stats->delta_time, 0); + + ecs_progress(world, 1.0); + + test_flt(stats->delta_time, 1.0); + + ecs_fini(world); +} + +void World_get_delta_time_auto() { + ecs_world_t *world = ecs_init(); + + const ecs_world_info_t *stats = ecs_get_world_info(world); + + test_int(stats->delta_time, 0); + + ecs_progress(world, 0); + + test_assert(stats->delta_time != 0); + + ecs_fini(world); +} + +void World_recreate_world() { + ecs_world_t *world = ecs_init(); + + test_assert(ecs_fini(world) == 0); + + world = ecs_init(); + + test_assert(ecs_fini(world) == 0); +} + +void World_recreate_world_w_component() { + ecs_world_t *world = ecs_init(); + test_assert(world != NULL); + + { + ECS_COMPONENT(world, Position); + test_assert(ecs_id(Position) != 0); + } + + test_assert(ecs_fini(world) == 0); + + { + world = ecs_init(); + test_assert(world != NULL); + + ECS_COMPONENT(world, Position); + test_assert(ecs_id(Position) != 0); + + test_assert(ecs_fini(world) == 0); + } +} + +void World_no_threading() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.mutex_new_ = NULL; + ecs_os_set_api(&os_api); + + ecs_world_t *world = ecs_init(); + test_assert(world != NULL); + ecs_fini(world); +} + +void World_no_time() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.get_time_ = NULL; + ecs_os_set_api(&os_api); + + ecs_world_t *world = ecs_init(); + test_assert(world != NULL); + ecs_fini(world); +} + +void World_is_entity_enabled() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new(world, 0); + + test_assert( ecs_has_entity(world, e, EcsDisabled) == false); + + ecs_fini(world); +} + +void World_get_stats() { + ecs_world_t *world = ecs_init(); + + ecs_world_stats_t stats = {0}; + ecs_get_world_stats(world, &stats); + + test_int(stats.t, 1); + + ecs_fini(world); +} + +static int zero_time_scale_invoked = 0; + +void ZeroTimeScale(ecs_iter_t *it) { + test_assert(it->delta_time == 0.0); + zero_time_scale_invoked ++; +} + +void World_system_time_scale() { + ecs_world_t *world = ecs_init(); + + ECS_TAG(world, Tag); + + ecs_new_w_id(world, Tag); + + ecs_set_time_scale(world, 0); + + ECS_SYSTEM(world, ZeroTimeScale, EcsOnUpdate, Tag); + + ecs_progress(world, 0); + ecs_progress(world, 0); + + const ecs_world_info_t *info = ecs_get_world_info(world); + test_assert(info->delta_time == 0.0); + + test_int(zero_time_scale_invoked, 2); + + ecs_fini(world); +} + +void World_ensure_empty_root() { + ecs_world_t *world = ecs_init(); + + ecs_query_t *q = ecs_query_new(world, "!(ChildOf, *)"); + ecs_iter_t it = ecs_query_iter(q); + + /* Make sure that the only entity in the root is the flecs module */ + + test_assert(ecs_query_next(&it)); + test_int(it.count, 1); + test_assert(it.entities[0] == EcsFlecs); + + test_assert(!ecs_query_next(&it)); + + ecs_fini(world); +} + +void World_register_alias_twice_same_entity() { + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + + ecs_use(world, e, "Foo"); + ecs_use(world, e, "Foo"); + + ecs_entity_t f = ecs_lookup(world, "Foo"); + test_assert(f == e); + + ecs_fini(world); +} + +void World_register_alias_twice_different_entity() { + install_test_abort(); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t e = ecs_new_id(world); + ecs_entity_t f = ecs_new_id(world); + + ecs_use(world, e, "Foo"); + + test_expect_abort(); + ecs_use(world, f, "Foo"); +} diff --git a/fggl/ecs2/flecs/test/api/src/main.c b/fggl/ecs2/flecs/test/api/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..2ca916001ab02ebc8be93909fa2e22f5c91b4033 --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/main.c @@ -0,0 +1,10079 @@ + +/* A friendly warning from bake.test + * ---------------------------------------------------------------------------- + * This file is generated. To add/remove testcases modify the 'project.json' of + * the test project. ANY CHANGE TO THIS FILE IS LOST AFTER (RE)BUILDING! + * ---------------------------------------------------------------------------- + */ + +#include <api.h> + +// Testsuite 'Entity' +void Entity_init_id(void); +void Entity_init_id_name(void); +void Entity_init_id_path(void); +void Entity_init_id_add_1_comp(void); +void Entity_init_id_add_2_comp(void); +void Entity_init_id_w_scope(void); +void Entity_init_id_name_w_scope(void); +void Entity_init_id_path_w_scope(void); +void Entity_init_id_name_1_comp(void); +void Entity_init_id_name_2_comp(void); +void Entity_init_id_name_2_comp_w_scope(void); +void Entity_id_add_1_comp(void); +void Entity_id_add_2_comp(void); +void Entity_id_remove_1_comp(void); +void Entity_id_remove_2_comp(void); +void Entity_init_id_path_w_sep(void); +void Entity_find_id_name(void); +void Entity_find_w_existing_id_name(void); +void Entity_find_id_name_w_scope(void); +void Entity_find_id_path(void); +void Entity_find_id_path_w_scope(void); +void Entity_find_id_name_match(void); +void Entity_find_id_name_match_w_scope(void); +void Entity_find_id_path_match(void); +void Entity_find_id_path_match_w_scope(void); +void Entity_find_id_name_mismatch(void); +void Entity_find_id_name_mismatch_w_scope(void); +void Entity_find_id_path_mismatch(void); +void Entity_find_id_path_mismatch_w_scope(void); +void Entity_find_id_add_1_comp(void); +void Entity_find_id_add_2_comp(void); +void Entity_find_id_remove_1_comp(void); +void Entity_find_id_remove_2_comp(void); +void Entity_init_w_scope_name(void); +void Entity_init_w_core_name(void); +void Entity_init_w_with(void); +void Entity_init_w_with_w_name(void); +void Entity_init_w_with_w_scope(void); +void Entity_init_w_with_w_name_scope(void); +void Entity_is_valid(void); +void Entity_is_recycled_valid(void); +void Entity_is_0_valid(void); +void Entity_is_junk_valid(void); +void Entity_is_not_alive_valid(void); + +// Testsuite 'New' +void New_setup(void); +void New_empty(void); +void New_component(void); +void New_type(void); +void New_type_of_2(void); +void New_type_w_type(void); +void New_type_w_2_types(void); +void New_type_mixed(void); +void New_tag(void); +void New_type_w_tag(void); +void New_type_w_2_tags(void); +void New_type_w_tag_mixed(void); +void New_redefine_component(void); +void New_recycle_id_empty(void); +void New_recycle_id_w_entity(void); +void New_recycle_id_w_type(void); +void New_recycle_empty_staged_delete(void); +void New_recycle_staged_delete(void); +void New_new_id(void); +void New_new_component_id(void); +void New_new_hi_component_id(void); +void New_new_component_id_skip_used(void); +void New_new_component_id_skip_to_hi_id(void); +void New_new_w_entity_0(void); +void New_create_w_explicit_id_2_worlds(void); +void New_new_w_id_0_w_with(void); +void New_new_w_id_w_with(void); +void New_new_w_type_0_w_with(void); +void New_new_w_type_w_with(void); +void New_new_w_id_w_with_w_scope(void); +void New_new_w_type_w_with_w_scope(void); +void New_new_w_id_w_with_defer(void); +void New_new_w_id_w_with_defer_w_scope(void); +void New_new_w_type_w_with_defer(void); +void New_new_w_type_w_with_defer_w_scope(void); + +// Testsuite 'New_w_Count' +void New_w_Count_empty(void); +void New_w_Count_component(void); +void New_w_Count_type(void); +void New_w_Count_type_of_2(void); +void New_w_Count_type_w_type(void); +void New_w_Count_type_w_2_types(void); +void New_w_Count_type_mixed(void); +void New_w_Count_tag(void); +void New_w_Count_type_w_tag(void); +void New_w_Count_type_w_2_tags(void); +void New_w_Count_type_w_tag_mixed(void); +void New_w_Count_new_w_data_1_comp(void); +void New_w_Count_new_w_data_2_comp(void); +void New_w_Count_new_w_data_w_tag(void); +void New_w_Count_new_w_data_w_comp_and_tag(void); +void New_w_Count_new_w_data_pair(void); +void New_w_Count_new_w_data_pair(void); +void New_w_Count_new_w_data_2_comp_1_not_set(void); +void New_w_Count_new_w_on_add_on_set_monitor(void); +void New_w_Count_new_w_data_override_set_comp(void); +void New_w_Count_new_w_data_override_set_pair(void); + +// Testsuite 'Add' +void Add_zero(void); +void Add_component(void); +void Add_component_again(void); +void Add_2_components(void); +void Add_2_components_again(void); +void Add_2_components_overlap(void); +void Add_type(void); +void Add_type_of_2(void); +void Add_type_w_type(void); +void Add_type_w_2_types(void); +void Add_type_mixed(void); +void Add_type_again(void); +void Add_type_overlap(void); +void Add_type_again_component(void); +void Add_type_overlap_component(void); +void Add_component_to_nonempty(void); +void Add_component_to_nonempty_again(void); +void Add_component_to_nonempty_overlap(void); +void Add_type_to_nonempty(void); +void Add_type_to_nonempty_again(void); +void Add_type_to_nonempty_overlap(void); +void Add_tag(void); +void Add_type_w_tag(void); +void Add_type_w_2_tags(void); +void Add_type_w_tag_mixed(void); +void Add_add_remove(void); +void Add_add_remove_same(void); +void Add_add_remove_entity(void); +void Add_add_remove_entity_same(void); +void Add_add_2_remove(void); +void Add_add_entity(void); +void Add_remove_entity(void); +void Add_add_0_entity(void); +void Add_remove_0_entity(void); +void Add_add_w_xor(void); +void Add_add_same_w_xor(void); +void Add_add_after_remove_xor(void); + +// Testsuite 'Switch' +void Switch_setup(void); +void Switch_get_case_empty(void); +void Switch_get_case_no_switch(void); +void Switch_get_case_unset(void); +void Switch_get_case_set(void); +void Switch_get_case_change(void); +void Switch_remove_case(void); +void Switch_remove_last(void); +void Switch_delete_first(void); +void Switch_delete_last(void); +void Switch_delete_first_last(void); +void Switch_3_entities_same_case(void); +void Switch_2_entities_1_change_case(void); +void Switch_3_entities_change_case(void); +void Switch_query_switch(void); +void Switch_query_1_case_1_type(void); +void Switch_query_1_case_2_types(void); +void Switch_query_2_cases_1_type(void); +void Switch_query_2_cases_2_types(void); +void Switch_query_after_remove(void); +void Switch_add_switch_in_stage(void); +void Switch_add_case_in_stage(void); +void Switch_change_case_in_stage(void); +void Switch_change_one_case_in_stage(void); +void Switch_remove_switch_in_stage(void); +void Switch_switch_no_match_for_case(void); +void Switch_empty_entity_has_case(void); +void Switch_zero_entity_has_case(void); +void Switch_add_to_entity_w_switch(void); +void Switch_add_pair_to_entity_w_switch(void); +void Switch_sort(void); +void Switch_recycled_tags(void); +void Switch_query_recycled_tags(void); +void Switch_single_case(void); + +// Testsuite 'EnabledComponents' +void EnabledComponents_is_component_enabled(void); +void EnabledComponents_is_empty_entity_disabled(void); +void EnabledComponents_is_0_entity_disabled(void); +void EnabledComponents_is_0_component_disabled(void); +void EnabledComponents_is_nonexist_component_disabled(void); +void EnabledComponents_is_enabled_component_enabled(void); +void EnabledComponents_is_disabled_component_enabled(void); +void EnabledComponents_has_enabled_component(void); +void EnabledComponents_is_enabled_after_add(void); +void EnabledComponents_is_enabled_after_remove(void); +void EnabledComponents_is_enabled_after_disable(void); +void EnabledComponents_is_disabled_after_enable(void); +void EnabledComponents_is_enabled_randomized(void); +void EnabledComponents_is_enabled_after_add_randomized(void); +void EnabledComponents_is_enabled_after_randomized_add_randomized(void); +void EnabledComponents_is_enabled_2(void); +void EnabledComponents_is_enabled_3(void); +void EnabledComponents_is_enabled_2_after_add(void); +void EnabledComponents_is_enabled_3_after_add(void); +void EnabledComponents_query_disabled(void); +void EnabledComponents_query_disabled_skip_initial(void); +void EnabledComponents_query_mod_2(void); +void EnabledComponents_query_mod_8(void); +void EnabledComponents_query_mod_64(void); +void EnabledComponents_query_mod_256(void); +void EnabledComponents_query_mod_1024(void); +void EnabledComponents_query_enable_mod_10(void); +void EnabledComponents_query_mod_2_2_bitsets(void); +void EnabledComponents_query_mod_8_2_bitsets(void); +void EnabledComponents_query_mod_64_2_bitsets(void); +void EnabledComponents_query_mod_256_2_bitsets(void); +void EnabledComponents_query_mod_1024_2_bitsets(void); +void EnabledComponents_query_randomized_2_bitsets(void); +void EnabledComponents_query_randomized_3_bitsets(void); +void EnabledComponents_query_randomized_4_bitsets(void); +void EnabledComponents_defer_enable(void); +void EnabledComponents_sort(void); + +// Testsuite 'Remove' +void Remove_zero(void); +void Remove_zero_from_nonzero(void); +void Remove_1_of_1(void); +void Remove_1_of_1_again(void); +void Remove_1_of_2(void); +void Remove_2_of_2(void); +void Remove_2_of_3(void); +void Remove_2_again(void); +void Remove_2_overlap(void); +void Remove_type_of_1_of_2(void); +void Remove_type_of_2_of_2(void); +void Remove_type_of_2_of_3(void); +void Remove_1_from_empty(void); +void Remove_type_from_empty(void); +void Remove_not_added(void); + +// Testsuite 'Parser' +void Parser_resolve_this(void); +void Parser_resolve_wildcard(void); +void Parser_resolve_is_a(void); +void Parser_0(void); +void Parser_component_implicit_subject(void); +void Parser_component_explicit_subject(void); +void Parser_component_explicit_subject_this(void); +void Parser_component_explicit_subject_this_by_name(void); +void Parser_component_explicit_subject_wildcard(void); +void Parser_pair_implicit_subject(void); +void Parser_pair_implicit_subject_wildcard_pred(void); +void Parser_pair_implicit_subject_wildcard_obj(void); +void Parser_pair_implicit_subject_this_pred(void); +void Parser_pair_implicit_subject_this_obj(void); +void Parser_pair_explicit_subject(void); +void Parser_pair_explicit_subject_this(void); +void Parser_pair_explicit_subject_this_by_name(void); +void Parser_pair_explicit_subject_wildcard_pred(void); +void Parser_pair_explicit_subject_wildcard_subj(void); +void Parser_pair_explicit_subject_wildcard_obj(void); +void Parser_in_component_implicit_subject(void); +void Parser_in_component_explicit_subject(void); +void Parser_in_pair_implicit_subject(void); +void Parser_in_pair_explicit_subject(void); +void Parser_inout_component_implicit_subject(void); +void Parser_inout_component_explicit_subject(void); +void Parser_inout_pair_implicit_subject(void); +void Parser_inout_pair_explicit_subject(void); +void Parser_out_component_implicit_subject(void); +void Parser_out_component_explicit_subject(void); +void Parser_out_pair_implicit_subject(void); +void Parser_out_pair_explicit_subject(void); +void Parser_component_singleton(void); +void Parser_this_singleton(void); +void Parser_component_implicit_no_subject(void); +void Parser_component_explicit_no_subject(void); +void Parser_pair_no_subject(void); +void Parser_variable_single_char(void); +void Parser_variable_multi_char(void); +void Parser_variable_multi_char_w_underscore(void); +void Parser_variable_multi_char_w_number(void); +void Parser_escaped_all_caps_single_char(void); +void Parser_escaped_all_caps_multi_char(void); +void Parser_component_not(void); +void Parser_pair_implicit_subject_not(void); +void Parser_pair_explicit_subject_not(void); +void Parser_2_component_not(void); +void Parser_2_component_not_no_space(void); +void Parser_component_optional(void); +void Parser_2_component_optional(void); +void Parser_2_component_optional_no_space(void); +void Parser_from_and(void); +void Parser_from_or(void); +void Parser_from_not(void); +void Parser_pair_implicit_subject_optional(void); +void Parser_pair_explicit_subject_optional(void); +void Parser_pred_implicit_subject_w_role(void); +void Parser_pred_explicit_subject_w_role(void); +void Parser_pred_no_subject_w_role(void); +void Parser_pair_implicit_subject_w_role(void); +void Parser_pair_explicit_subject_w_role(void); +void Parser_inout_role_pred_implicit_subject(void); +void Parser_inout_role_pred_no_subject(void); +void Parser_inout_role_pred_explicit_subject(void); +void Parser_inout_role_pair_implicit_subject(void); +void Parser_inout_role_pair_explicit_subject(void); +void Parser_2_pred_implicit_subject(void); +void Parser_2_pred_no_subject(void); +void Parser_2_pred_explicit_subject(void); +void Parser_2_pair_implicit_subject(void); +void Parser_2_pair_explicit_subject(void); +void Parser_2_pred_role(void); +void Parser_2_pair_implicit_subj_role(void); +void Parser_2_pair_explicit_subj_role(void); +void Parser_2_or_pred_implicit_subj(void); +void Parser_2_or_pred_explicit_subj(void); +void Parser_2_or_pair_implicit_subj(void); +void Parser_2_or_pair_explicit_subj(void); +void Parser_2_or_pred_inout(void); +void Parser_1_digit_pred_implicit_subj(void); +void Parser_1_digit_pred_no_subj(void); +void Parser_1_digit_pred_explicit_subj(void); +void Parser_1_digit_pair_implicit_subj(void); +void Parser_1_digit_pair_explicit_subj(void); +void Parser_pred_implicit_subject_self(void); +void Parser_pred_implicit_subject_superset(void); +void Parser_pred_implicit_subject_subset(void); +void Parser_pred_implicit_subject_superset_inclusive(void); +void Parser_pred_implicit_subject_subset_inclusive(void); +void Parser_pred_implicit_subject_superset_cascade(void); +void Parser_pred_implicit_subject_subset_cascade(void); +void Parser_pred_implicit_subject_superset_inclusive_cascade(void); +void Parser_pred_implicit_subject_subset_inclusive_cascade(void); +void Parser_pred_implicit_subject_implicit_superset_cascade(void); +void Parser_pred_implicit_subject_implicit_superset_inclusive_cascade(void); +void Parser_pred_implicit_subject_implicit_superset_cascade_w_rel(void); +void Parser_pred_implicit_subject_implicit_superset_inclusive_cascade_w_rel(void); +void Parser_pred_implicit_subject_superset_depth_1_digit(void); +void Parser_pred_implicit_subject_subset_depth_1_digit(void); +void Parser_pred_implicit_subject_superset_depth_2_digits(void); +void Parser_pred_implicit_subject_subset_depth_2_digits(void); +void Parser_pred_implicit_superset_min_max_depth(void); +void Parser_pred_implicit_superset_childof_min_max_depth(void); +void Parser_pred_implicit_subject_superset_childof(void); +void Parser_pred_implicit_subject_cascade_superset_childof(void); +void Parser_pred_implicit_subject_superset_cascade_childof(void); +void Parser_pred_implicit_subject_superset_cascade_childof_optional(void); +void Parser_expr_w_symbol(void); + +// Testsuite 'Plecs' +void Plecs_null(void); +void Plecs_empty(void); +void Plecs_space(void); +void Plecs_space_newline(void); +void Plecs_empty_newline(void); +void Plecs_entity(void); +void Plecs_entity_w_entity(void); +void Plecs_entity_w_pair(void); +void Plecs_2_entities(void); +void Plecs_2_entities_w_entities(void); +void Plecs_3_entities_w_pairs(void); + +// Testsuite 'GlobalComponentIds' +void GlobalComponentIds_declare(void); +void GlobalComponentIds_declare_w_entity(void); +void GlobalComponentIds_declare_2_world(void); +void GlobalComponentIds_declare_tag(void); +void GlobalComponentIds_declare_tag_w_entity(void); +void GlobalComponentIds_declare_entity(void); +void GlobalComponentIds_declare_type(void); + +// Testsuite 'Hierarchies' +void Hierarchies_setup(void); +void Hierarchies_empty_scope(void); +void Hierarchies_get_parent(void); +void Hierarchies_get_parent_from_nested(void); +void Hierarchies_get_parent_from_nested_2(void); +void Hierarchies_get_parent_from_root(void); +void Hierarchies_tree_iter_empty(void); +void Hierarchies_tree_iter_1_table(void); +void Hierarchies_tree_iter_2_tables(void); +void Hierarchies_tree_iter_w_filter(void); +void Hierarchies_path_depth_0(void); +void Hierarchies_path_depth_1(void); +void Hierarchies_path_depth_2(void); +void Hierarchies_rel_path_from_root(void); +void Hierarchies_rel_path_from_self(void); +void Hierarchies_rel_path_depth_1(void); +void Hierarchies_rel_path_depth_2(void); +void Hierarchies_rel_path_no_match(void); +void Hierarchies_path_custom_sep(void); +void Hierarchies_path_custom_prefix(void); +void Hierarchies_path_prefix_rel_match(void); +void Hierarchies_path_prefix_rel_no_match(void); +void Hierarchies_fullpath_for_core(void); +void Hierarchies_path_w_number(void); +void Hierarchies_lookup_depth_0(void); +void Hierarchies_lookup_depth_1(void); +void Hierarchies_lookup_depth_2(void); +void Hierarchies_lookup_rel_0(void); +void Hierarchies_lookup_rel_1(void); +void Hierarchies_lookup_rel_2(void); +void Hierarchies_lookup_custom_sep(void); +void Hierarchies_lookup_custom_prefix(void); +void Hierarchies_lookup_custom_prefix_from_root(void); +void Hierarchies_lookup_self(void); +void Hierarchies_lookup_in_parent_from_scope(void); +void Hierarchies_lookup_in_root_from_scope(void); +void Hierarchies_lookup_number(void); +void Hierarchies_delete_children(void); +void Hierarchies_scope_set(void); +void Hierarchies_scope_set_again(void); +void Hierarchies_scope_set_w_new(void); +void Hierarchies_scope_set_w_new_staged(void); +void Hierarchies_scope_set_w_lookup(void); +void Hierarchies_scope_component(void); +void Hierarchies_scope_component_no_macro(void); +void Hierarchies_new_from_path_depth_0(void); +void Hierarchies_new_from_path_depth_1(void); +void Hierarchies_new_from_path_depth_2(void); +void Hierarchies_new_from_path_existing_depth_0(void); +void Hierarchies_new_from_path_existing_depth_1(void); +void Hierarchies_new_from_path_existing_depth_2(void); +void Hierarchies_add_path_depth_0(void); +void Hierarchies_add_path_depth_1(void); +void Hierarchies_add_path_depth_2(void); +void Hierarchies_add_path_existing_depth_0(void); +void Hierarchies_add_path_existing_depth_1(void); +void Hierarchies_add_path_existing_depth_2(void); +void Hierarchies_add_path_from_scope(void); +void Hierarchies_add_path_from_scope_new_entity(void); +void Hierarchies_new_w_child_in_root(void); +void Hierarchies_delete_child(void); +void Hierarchies_delete_2_children(void); +void Hierarchies_delete_2_children_different_type(void); +void Hierarchies_delete_tree_2_levels(void); +void Hierarchies_delete_tree_3_levels(void); +void Hierarchies_delete_tree_count_tables(void); +void Hierarchies_delete_tree_staged(void); +void Hierarchies_delete_tree_empty_table(void); +void Hierarchies_delete_tree_recreate(void); +void Hierarchies_delete_tree_w_onremove(void); +void Hierarchies_delete_tree_w_dtor(void); +void Hierarchies_get_child_count(void); +void Hierarchies_get_child_count_2_tables(void); +void Hierarchies_get_child_count_no_children(void); +void Hierarchies_scope_iter_after_delete_tree(void); +void Hierarchies_add_child_after_delete_tree(void); +void Hierarchies_add_child_to_recycled_parent(void); +void Hierarchies_get_type_after_recycled_parent_add(void); +void Hierarchies_rematch_after_add_to_recycled_parent(void); +void Hierarchies_cascade_after_recycled_parent_change(void); +void Hierarchies_long_name_depth_0(void); +void Hierarchies_long_name_depth_1(void); +void Hierarchies_long_name_depth_2(void); + +// Testsuite 'Add_bulk' +void Add_bulk_add_comp_from_comp_to_empty(void); +void Add_bulk_add_comp_from_comp_to_existing(void); +void Add_bulk_add_comp_from_tag_to_empty(void); +void Add_bulk_add_comp_from_tag_to_existing(void); +void Add_bulk_add_tag_from_tag_to_empty(void); +void Add_bulk_add_tag_from_tag_to_existing(void); +void Add_bulk_add_comp_to_more_existing(void); +void Add_bulk_add_comp_to_fewer_existing(void); +void Add_bulk_on_add(void); +void Add_bulk_add_entity_comp(void); +void Add_bulk_add_entity_tag(void); +void Add_bulk_add_entity_on_add(void); +void Add_bulk_add_entity_existing(void); + +// Testsuite 'Remove_bulk' +void Remove_bulk_remove_comp_from_comp_to_empty(void); +void Remove_bulk_remove_comp_from_comp_to_existing(void); +void Remove_bulk_remove_comp_from_tag_to_empty(void); +void Remove_bulk_remove_comp_from_tag_to_existing(void); +void Remove_bulk_remove_tag_from_tag_to_empty(void); +void Remove_bulk_remove_tag_from_tag_to_existing(void); +void Remove_bulk_remove_all_comp(void); +void Remove_bulk_remove_all_tag(void); +void Remove_bulk_on_remove(void); +void Remove_bulk_remove_entity_comp(void); +void Remove_bulk_remove_entity_tag(void); +void Remove_bulk_remove_entity_on_remove(void); +void Remove_bulk_bulk_remove_w_low_tag_id(void); + +// Testsuite 'Add_remove_bulk' +void Add_remove_bulk_add_remove_add_only(void); +void Add_remove_bulk_add_remove_remove_only(void); +void Add_remove_bulk_add_remove_both(void); +void Add_remove_bulk_add_remove_same(void); + +// Testsuite 'Has' +void Has_zero(void); +void Has_zero_from_nonzero(void); +void Has_1_of_0(void); +void Has_1_of_1(void); +void Has_1_of_2(void); +void Has_2_of_0(void); +void Has_2_of_2(void); +void Has_3_of_2(void); +void Has_2_of_1(void); +void Has_1_of_empty(void); +void Has_has_in_progress(void); +void Has_has_of_zero(void); +void Has_owns(void); +void Has_has_entity(void); +void Has_has_entity_0(void); +void Has_has_entity_0_component(void); +void Has_has_entity_owned(void); +void Has_has_entity_owned_0(void); +void Has_has_entity_owned_0_component(void); +void Has_has_wildcard(void); +void Has_has_wildcard_pair(void); + +// Testsuite 'Count' +void Count_count_empty(void); +void Count_count_w_entity_0(void); +void Count_count_1_component(void); +void Count_count_2_components(void); +void Count_count_3_components(void); +void Count_count_2_types_2_comps(void); + +// Testsuite 'Get_component' +void Get_component_setup(void); +void Get_component_get_empty(void); +void Get_component_get_1_from_1(void); +void Get_component_get_1_from_2(void); +void Get_component_get_2_from_2(void); +void Get_component_get_2_from_3(void); +void Get_component_get_1_from_2_in_progress_from_main_stage(void); +void Get_component_get_1_from_2_add_in_progress(void); +void Get_component_get_both_from_2_add_in_progress(void); +void Get_component_get_both_from_2_add_remove_in_progress(void); +void Get_component_get_childof_component(void); + +// Testsuite 'Reference' +void Reference_setup(void); +void Reference_get_ref(void); +void Reference_get_ref_after_add(void); +void Reference_get_ref_after_remove(void); +void Reference_get_ref_after_delete(void); +void Reference_get_ref_after_realloc(void); +void Reference_get_ref_after_realloc_w_lifecycle(void); +void Reference_get_ref_staged(void); +void Reference_get_ref_after_new_in_stage(void); +void Reference_get_ref_monitored(void); +void Reference_get_nonexisting(void); + +// Testsuite 'Delete' +void Delete_setup(void); +void Delete_delete_1(void); +void Delete_delete_1_again(void); +void Delete_delete_empty(void); +void Delete_delete_nonexist(void); +void Delete_delete_1st_of_3(void); +void Delete_delete_2nd_of_3(void); +void Delete_delete_2_of_3(void); +void Delete_delete_3_of_3(void); +void Delete_delete_w_on_remove(void); +void Delete_clear_1_component(void); +void Delete_clear_2_components(void); +void Delete_alive_after_delete(void); +void Delete_alive_after_clear(void); +void Delete_alive_after_staged_delete(void); +void Delete_alive_while_staged(void); +void Delete_alive_while_staged_w_delete(void); +void Delete_alive_while_staged_w_delete_recycled_id(void); +void Delete_alive_after_recycle(void); +void Delete_delete_recycled(void); +void Delete_get_alive_for_alive(void); +void Delete_get_alive_for_recycled(void); +void Delete_get_alive_for_not_alive(void); +void Delete_get_alive_w_generation_for_recycled_alive(void); +void Delete_get_alive_w_generation_for_recycled_not_alive(void); +void Delete_get_alive_for_0(void); +void Delete_get_alive_for_nonexistent(void); +void Delete_move_w_dtor_move(void); +void Delete_move_w_dtor_no_move(void); +void Delete_move_w_no_dtor_move(void); +void Delete_wrap_generation_count(void); + +// Testsuite 'OnDelete' +void OnDelete_on_delete_id_default(void); +void OnDelete_on_delete_id_remove(void); +void OnDelete_on_delete_id_delete(void); +void OnDelete_on_delete_relation_default(void); +void OnDelete_on_delete_relation_remove(void); +void OnDelete_on_delete_relation_delete(void); +void OnDelete_on_delete_object_default(void); +void OnDelete_on_delete_object_remove(void); +void OnDelete_on_delete_object_delete(void); +void OnDelete_on_delete_id_throw(void); +void OnDelete_on_delete_relation_throw(void); +void OnDelete_on_delete_object_throw(void); +void OnDelete_on_delete_id_remove_no_instances(void); +void OnDelete_on_delete_id_delete_no_instances(void); +void OnDelete_on_delete_id_throw_no_instances(void); +void OnDelete_on_delete_cyclic_id_default(void); +void OnDelete_on_delete_cyclic_id_remove(void); +void OnDelete_on_delete_cyclic_id_remove_both(void); +void OnDelete_on_delete_cyclic_id_delete(void); +void OnDelete_on_delete_cyclic_id_delete_both(void); +void OnDelete_on_delete_cyclic_relation_default(void); +void OnDelete_on_delete_cyclic_relation_remove(void); +void OnDelete_on_delete_cyclic_relation_remove_both(void); +void OnDelete_on_delete_cyclic_relation_delete(void); +void OnDelete_on_delete_cyclic_object_default(void); +void OnDelete_on_delete_cyclic_object_remove(void); +void OnDelete_on_delete_cyclic_object_delete(void); +void OnDelete_on_delete_remove_2_comps(void); +void OnDelete_on_delete_remove_2_comps_to_existing_table(void); +void OnDelete_on_delete_delete_recursive(void); +void OnDelete_on_delete_component_throw(void); +void OnDelete_on_delete_remove_2_relations(void); +void OnDelete_on_delete_remove_object_w_2_relations(void); +void OnDelete_on_delete_remove_object_w_5_relations(void); +void OnDelete_on_delete_remove_object_w_50_relations(void); +void OnDelete_on_delete_remove_object_w_50_relations_3_tables(void); +void OnDelete_remove_id_from_2_tables(void); +void OnDelete_remove_relation_from_2_tables(void); +void OnDelete_remove_object_from_2_tables(void); +void OnDelete_remove_id_and_relation(void); +void OnDelete_remove_id_and_relation_from_2_tables(void); +void OnDelete_stresstest_many_objects(void); +void OnDelete_stresstest_many_relations(void); +void OnDelete_stresstest_many_objects_on_delete(void); +void OnDelete_stresstest_many_relations_on_delete(void); +void OnDelete_on_delete_empty_table_w_on_remove(void); +void OnDelete_delete_table_in_on_remove_during_fini(void); +void OnDelete_delete_other_in_on_remove_during_fini(void); +void OnDelete_on_delete_remove_id_w_role(void); +void OnDelete_on_delete_merge_pair_component(void); + +// Testsuite 'Delete_w_filter' +void Delete_w_filter_delete_1(void); +void Delete_w_filter_delete_2(void); +void Delete_w_filter_delete_1_2_types(void); +void Delete_w_filter_delete_2_2_types(void); +void Delete_w_filter_delete_except_1(void); +void Delete_w_filter_delete_except_2(void); +void Delete_w_filter_delete_with_any_of_2(void); +void Delete_w_filter_delete_except_all_of_2(void); +void Delete_w_filter_include_exact(void); +void Delete_w_filter_exclude_exact(void); +void Delete_w_filter_system_activate_test(void); +void Delete_w_filter_skip_builtin_tables(void); +void Delete_w_filter_delete_w_on_remove(void); + +// Testsuite 'Set' +void Set_set_empty(void); +void Set_set_nonempty(void); +void Set_set_non_empty_override(void); +void Set_set_again(void); +void Set_set_2(void); +void Set_add_set(void); +void Set_set_add(void); +void Set_set_add_other(void); +void Set_set_remove(void); +void Set_set_remove_other(void); +void Set_set_remove_twice(void); +void Set_set_and_new(void); +void Set_set_null(void); +void Set_get_mut_new(void); +void Set_get_mut_existing(void); +void Set_get_mut_tag_new(void); +void Set_get_mut_tag_existing(void); +void Set_get_mut_tag_new_w_comp(void); +void Set_get_mut_tag_existing_w_comp(void); +void Set_get_mut_tag_new_w_pair(void); +void Set_get_mut_tag_existing_w_pair(void); +void Set_modified_w_on_set(void); +void Set_modified_no_component(void); +void Set_get_mut_w_add_in_on_add(void); +void Set_get_mut_w_remove_in_on_add(void); +void Set_emplace(void); +void Set_emplace_existing(void); +void Set_emplace_w_move(void); + +// Testsuite 'Lookup' +void Lookup_setup(void); +void Lookup_lookup(void); +void Lookup_lookup_component(void); +void Lookup_lookup_not_found(void); +void Lookup_lookup_child(void); +void Lookup_lookup_w_null_name(void); +void Lookup_get_name(void); +void Lookup_get_name_no_name(void); +void Lookup_get_name_from_empty(void); +void Lookup_lookup_by_id(void); +void Lookup_lookup_symbol_by_id(void); +void Lookup_lookup_name_w_digit(void); +void Lookup_lookup_symbol_w_digit(void); +void Lookup_lookup_path_w_digit(void); +void Lookup_set_name_of_existing(void); +void Lookup_change_name_of_existing(void); +void Lookup_lookup_alias(void); +void Lookup_lookup_scoped_alias(void); +void Lookup_define_duplicate_alias(void); +void Lookup_lookup_null(void); +void Lookup_lookup_symbol_null(void); +void Lookup_lookup_this(void); +void Lookup_lookup_wildcard(void); +void Lookup_lookup_path_this(void); +void Lookup_lookup_path_wildcard(void); +void Lookup_lookup_path_this_from_scope(void); +void Lookup_lookup_path_wildcard_from_scope(void); + +// Testsuite 'Singleton' +void Singleton_set_get_singleton(void); +void Singleton_get_mut_singleton(void); +void Singleton_singleton_system(void); + +// Testsuite 'Clone' +void Clone_empty(void); +void Clone_empty_w_value(void); +void Clone_null(void); +void Clone_null_w_value(void); +void Clone_1_component(void); +void Clone_2_component(void); +void Clone_1_component_w_value(void); +void Clone_2_component_w_value(void); +void Clone_3_component(void); +void Clone_3_component_w_value(void); +void Clone_tag(void); +void Clone_tag_w_value(void); +void Clone_1_tag_1_component(void); +void Clone_1_tag_1_component_w_value(void); + +// Testsuite 'ComponentLifecycle' +void ComponentLifecycle_setup(void); +void ComponentLifecycle_ctor_on_add(void); +void ComponentLifecycle_ctor_on_new(void); +void ComponentLifecycle_dtor_on_remove(void); +void ComponentLifecycle_dtor_on_delete(void); +void ComponentLifecycle_copy_on_set(void); +void ComponentLifecycle_copy_on_override(void); +void ComponentLifecycle_copy_on_new_w_data(void); +void ComponentLifecycle_copy_on_clone(void); +void ComponentLifecycle_no_copy_on_move(void); +void ComponentLifecycle_ctor_on_bulk_add(void); +void ComponentLifecycle_dtor_on_bulk_remove(void); +void ComponentLifecycle_ctor_on_bulk_add_entity(void); +void ComponentLifecycle_dtor_on_bulk_remove_entity(void); +void ComponentLifecycle_ctor_dtor_on_bulk_add_remove(void); +void ComponentLifecycle_ctor_copy_on_snapshot(void); +void ComponentLifecycle_copy_on_snapshot(void); +void ComponentLifecycle_dtor_on_restore(void); +void ComponentLifecycle_ctor_on_tag(void); +void ComponentLifecycle_dtor_on_tag(void); +void ComponentLifecycle_copy_on_tag(void); +void ComponentLifecycle_move_on_tag(void); +void ComponentLifecycle_merge_to_different_table(void); +void ComponentLifecycle_merge_to_new_table(void); +void ComponentLifecycle_delete_in_stage(void); +void ComponentLifecycle_ctor_on_add_pair(void); +void ComponentLifecycle_ctor_on_add_pair_set_ctor_after_table(void); +void ComponentLifecycle_ctor_on_add_pair_tag(void); +void ComponentLifecycle_ctor_on_add_pair_tag_set_ctor_after_table(void); +void ComponentLifecycle_ctor_on_move_pair(void); +void ComponentLifecycle_move_on_realloc(void); +void ComponentLifecycle_move_on_dim(void); +void ComponentLifecycle_move_on_bulk_new(void); +void ComponentLifecycle_move_on_delete(void); +void ComponentLifecycle_move_dtor_on_delete(void); +void ComponentLifecycle_copy_on_override_pair(void); +void ComponentLifecycle_copy_on_override_pair_tag(void); +void ComponentLifecycle_copy_on_set_pair(void); +void ComponentLifecycle_copy_on_set_pair_tag(void); +void ComponentLifecycle_prevent_lifecycle_overwrite(void); +void ComponentLifecycle_prevent_lifecycle_overwrite_null_callbacks(void); +void ComponentLifecycle_allow_lifecycle_overwrite_equal_callbacks(void); +void ComponentLifecycle_set_lifecycle_after_trigger(void); +void ComponentLifecycle_valid_entity_in_dtor_after_delete(void); +void ComponentLifecycle_ctor_w_emplace(void); +void ComponentLifecycle_dtor_on_fini(void); +void ComponentLifecycle_valid_type_in_dtor_on_fini(void); +void ComponentLifecycle_valid_other_type_of_entity_in_dtor_on_fini(void); +void ComponentLifecycle_valid_same_type_comp_of_entity_in_dtor_on_fini(void); +void ComponentLifecycle_valid_same_type_comp_of_entity_in_dtor_on_delete_parent(void); +void ComponentLifecycle_valid_entity_bulk_remove_all_components(void); +void ComponentLifecycle_delete_in_dtor_same_type_on_fini(void); +void ComponentLifecycle_delete_in_dtor_other_type_on_fini(void); +void ComponentLifecycle_delete_self_in_dtor_on_fini(void); +void ComponentLifecycle_delete_in_dtor_same_type_on_delete_parent(void); +void ComponentLifecycle_delete_in_dtor_other_type_on_delete_parent(void); +void ComponentLifecycle_delete_self_in_dtor_on_delete_parent(void); +void ComponentLifecycle_delete_in_dtor_same_type_on_delete(void); +void ComponentLifecycle_delete_in_dtor_other_type_on_delete(void); +void ComponentLifecycle_delete_self_in_dtor_on_delete(void); +void ComponentLifecycle_on_set_after_set(void); +void ComponentLifecycle_on_set_after_new_w_data(void); + +// Testsuite 'Pipeline' +void Pipeline_setup(void); +void Pipeline_system_order_same_phase(void); +void Pipeline_system_order_same_phase_after_disable(void); +void Pipeline_system_order_same_phase_after_activate(void); +void Pipeline_system_order_different_phase(void); +void Pipeline_system_order_different_phase_after_disable(void); +void Pipeline_system_order_different_phase_after_activate(void); +void Pipeline_system_order_after_new_system_lower_id(void); +void Pipeline_system_order_after_new_system_inbetween_id(void); +void Pipeline_system_order_after_new_system_higher_id(void); +void Pipeline_merge_after_staged_out(void); +void Pipeline_merge_after_not_out(void); +void Pipeline_no_merge_after_main_out(void); +void Pipeline_no_merge_after_staged_in_out(void); +void Pipeline_merge_after_staged_out_before_owned(void); +void Pipeline_switch_pipeline(void); +void Pipeline_run_pipeline(void); +void Pipeline_get_pipeline_from_stage(void); +void Pipeline_3_systems_3_types(void); +void Pipeline_random_read_after_random_write_out_in(void); +void Pipeline_random_read_after_random_write_inout_in(void); +void Pipeline_random_read_after_random_write_out_inout(void); +void Pipeline_random_read_after_random_write_inout_inout(void); +void Pipeline_random_read_after_random_write_w_not_write(void); +void Pipeline_random_read_after_random_write_w_not_read(void); +void Pipeline_random_read_after_random_write_w_wildcard(void); +void Pipeline_random_in_after_random_inout_after_random_out(void); + +// Testsuite 'SystemMisc' +void SystemMisc_setup(void); +void SystemMisc_invalid_not_without_id(void); +void SystemMisc_invalid_optional_without_id(void); +void SystemMisc_invalid_system_without_id(void); +void SystemMisc_invalid_container_without_id(void); +void SystemMisc_invalid_cascade_without_id(void); +void SystemMisc_invalid_entity_without_id(void); +void SystemMisc_invalid_empty_without_id(void); +void SystemMisc_invalid_empty_element(void); +void SystemMisc_invalid_empty_element_w_space(void); +void SystemMisc_invalid_empty_or(void); +void SystemMisc_invalid_empty_or_w_space(void); +void SystemMisc_invalid_or_w_not(void); +void SystemMisc_invalid_not_w_or(void); +void SystemMisc_invalid_0_w_and(void); +void SystemMisc_invalid_0_w_from_system(void); +void SystemMisc_invalid_0_w_from_container(void); +void SystemMisc_invalid_0_w_from_cascade(void); +void SystemMisc_invalid_0_w_from_entity(void); +void SystemMisc_invalid_0_w_from_empty(void); +void SystemMisc_invalid_or_w_empty(void); +void SystemMisc_invalid_component_id(void); +void SystemMisc_invalid_entity_id(void); +void SystemMisc_invalid_null_string(void); +void SystemMisc_invalid_empty_string(void); +void SystemMisc_invalid_empty_string_w_space(void); +void SystemMisc_invalid_mixed_src_modifier(void); +void SystemMisc_redefine_row_system(void); +void SystemMisc_system_w_or_prefab(void); +void SystemMisc_system_w_or_disabled(void); +void SystemMisc_system_w_or_disabled_and_prefab(void); +void SystemMisc_table_columns_access(void); +void SystemMisc_status_enable_after_new(void); +void SystemMisc_status_enable_after_disable(void); +void SystemMisc_status_disable_after_new(void); +void SystemMisc_status_disable_after_disable(void); +void SystemMisc_status_activate_after_new(void); +void SystemMisc_status_deactivate_after_delete(void); +void SystemMisc_dont_enable_after_rematch(void); +void SystemMisc_ensure_single_merge(void); +void SystemMisc_table_count(void); +void SystemMisc_match_system(void); +void SystemMisc_match_system_w_filter(void); +void SystemMisc_system_initial_state(void); +void SystemMisc_add_own_component(void); +void SystemMisc_change_system_action(void); +void SystemMisc_system_readeactivate(void); +void SystemMisc_system_readeactivate_w_2_systems(void); +void SystemMisc_add_to_system_in_progress(void); +void SystemMisc_add_to_lazy_system_in_progress(void); +void SystemMisc_redefine_null_signature(void); +void SystemMisc_redefine_0_signature(void); +void SystemMisc_one_named_column_of_two(void); +void SystemMisc_two_named_columns_of_two(void); +void SystemMisc_get_column_by_name(void); +void SystemMisc_get_column_by_name_not_found(void); +void SystemMisc_get_column_by_name_no_names(void); +void SystemMisc_redeclare_system_same_expr(void); +void SystemMisc_redeclare_system_null_expr(void); +void SystemMisc_redeclare_system_0_expr(void); +void SystemMisc_redeclare_system_different_expr(void); +void SystemMisc_redeclare_system_null_and_expr(void); +void SystemMisc_redeclare_system_expr_and_null(void); +void SystemMisc_redeclare_system_expr_and_0(void); +void SystemMisc_redeclare_system_0_and_expr(void); +void SystemMisc_redeclare_system_0_and_null(void); +void SystemMisc_redeclare_system_null_and_0(void); +void SystemMisc_redeclare_system_explicit_id(void); +void SystemMisc_redeclare_system_explicit_id_null_expr(void); +void SystemMisc_redeclare_system_explicit_id_no_name(void); +void SystemMisc_declare_different_id_same_name(void); +void SystemMisc_declare_different_id_same_name_w_scope(void); +void SystemMisc_rw_in_implicit_any(void); +void SystemMisc_rw_in_implicit_shared(void); +void SystemMisc_rw_in_implicit_from_empty(void); +void SystemMisc_rw_in_implicit_from_entity(void); +void SystemMisc_rw_out_explicit_any(void); +void SystemMisc_rw_out_explicit_shared(void); +void SystemMisc_rw_out_explicit_from_empty(void); +void SystemMisc_rw_out_explicit_from_entity(void); +void SystemMisc_activate_system_for_table_w_n_pairs(void); +void SystemMisc_get_query(void); +void SystemMisc_set_get_context(void); +void SystemMisc_set_get_binding_context(void); +void SystemMisc_deactivate_after_disable(void); +void SystemMisc_system_w_self(void); +void SystemMisc_delete_system(void); +void SystemMisc_delete_pipeline_system(void); +void SystemMisc_delete_system_w_ctx(void); + +// Testsuite 'Sorting' +void Sorting_sort_by_component(void); +void Sorting_sort_by_component_2_tables(void); +void Sorting_sort_by_component_3_tables(void); +void Sorting_sort_by_entity(void); +void Sorting_sort_after_add(void); +void Sorting_sort_after_remove(void); +void Sorting_sort_after_delete(void); +void Sorting_sort_after_set(void); +void Sorting_sort_after_system(void); +void Sorting_sort_after_query(void); +void Sorting_sort_by_component_same_value_1(void); +void Sorting_sort_by_component_same_value_2(void); +void Sorting_sort_by_component_move_pivot(void); +void Sorting_sort_1000_entities(void); +void Sorting_sort_1000_entities_w_duplicates(void); +void Sorting_sort_1000_entities_again(void); +void Sorting_sort_1000_entities_2_types(void); +void Sorting_sort_1500_entities_3_types(void); +void Sorting_sort_2000_entities_4_types(void); +void Sorting_sort_2_entities_2_types(void); +void Sorting_sort_3_entities_3_types(void); +void Sorting_sort_3_entities_3_types_2(void); +void Sorting_sort_4_entities_4_types(void); +void Sorting_sort_1000_entities_2_types_again(void); +void Sorting_sort_1000_entities_add_type_after_sort(void); +void Sorting_sort_shared_component(void); +void Sorting_sort_w_tags_only(void); +void Sorting_sort_childof_marked(void); +void Sorting_sort_isa_marked(void); +void Sorting_sort_relation_marked(void); + +// Testsuite 'Filter' +void Filter_filter_1_term(void); +void Filter_filter_2_terms(void); +void Filter_filter_3_terms(void); +void Filter_filter_3_terms_w_or(void); +void Filter_filter_4_terms_w_or_at_1(void); +void Filter_filter_w_pair_id(void); +void Filter_filter_w_pred_obj(void); +void Filter_filter_move(void); +void Filter_filter_copy(void); +void Filter_filter_w_resources_copy(void); +void Filter_filter_w_10_terms(void); +void Filter_filter_w_10_terms_move(void); +void Filter_filter_w_10_terms_copy(void); +void Filter_term_w_id(void); +void Filter_term_w_pair_id(void); +void Filter_term_w_pred_obj(void); +void Filter_term_w_pair_finalize_twice(void); +void Filter_term_w_role(void); +void Filter_term_w_pred_role(void); +void Filter_term_iter_component(void); +void Filter_term_iter_tag(void); +void Filter_term_iter_pair(void); +void Filter_term_iter_pair_w_rel_wildcard(void); +void Filter_term_iter_pair_w_obj_wildcard(void); +void Filter_term_iter_w_superset(void); +void Filter_term_iter_w_superset_childof(void); +void Filter_term_iter_w_superset_self(void); +void Filter_term_iter_w_superset_self_childof(void); +void Filter_term_iter_w_superset_tag(void); +void Filter_term_iter_w_superset_pair(void); +void Filter_term_iter_w_superset_pair_obj_wildcard(void); +void Filter_term_iter_in_stage(void); +void Filter_filter_iter_1_tag(void); +void Filter_filter_iter_2_tags(void); +void Filter_filter_iter_2_tags_1_not(void); +void Filter_filter_iter_3_tags_2_or(void); +void Filter_filter_iter_1_component(void); +void Filter_filter_iter_2_components(void); +void Filter_filter_iter_pair_id(void); +void Filter_filter_iter_2_pair_ids(void); +void Filter_filter_iter_pair_pred_obj(void); +void Filter_filter_iter_pair_2_pred_obj(void); +void Filter_filter_iter_null(void); +void Filter_filter_iter_1_not_tag(void); +void Filter_filter_iter_2_tags_1_optional(void); +void Filter_filter_iter_in_stage(void); +void Filter_filter_iter_10_tags(void); +void Filter_filter_iter_20_tags(void); +void Filter_filter_iter_10_components(void); +void Filter_filter_iter_20_components(void); + +// Testsuite 'Query' +void Query_query_changed_after_new(void); +void Query_query_changed_after_delete(void); +void Query_query_changed_after_add(void); +void Query_query_changed_after_remove(void); +void Query_query_changed_after_set(void); +void Query_query_change_after_modified(void); +void Query_query_change_after_out_system(void); +void Query_query_change_after_in_system(void); +void Query_subquery_match_existing(void); +void Query_subquery_match_new(void); +void Query_subquery_inactive(void); +void Query_subquery_unmatch(void); +void Query_subquery_rematch(void); +void Query_subquery_rematch_w_parent_optional(void); +void Query_subquery_rematch_w_sub_optional(void); +void Query_query_single_pairs(void); +void Query_query_single_instanceof(void); +void Query_query_single_childof(void); +void Query_query_w_filter(void); +void Query_query_optional_owned(void); +void Query_query_optional_shared(void); +void Query_query_optional_shared_nested(void); +void Query_query_optional_any(void); +void Query_query_rematch_optional_after_add(void); +void Query_get_owned_tag(void); +void Query_get_shared_tag(void); +void Query_explicit_delete(void); +void Query_get_column_size(void); +void Query_orphaned_query(void); +void Query_nested_orphaned_query(void); +void Query_invalid_access_orphaned_query(void); +void Query_stresstest_query_free(void); +void Query_only_from_entity(void); +void Query_only_from_singleton(void); +void Query_only_not_from_entity(void); +void Query_only_not_from_singleton(void); +void Query_get_filter(void); +void Query_group_by(void); +void Query_group_by_w_ctx(void); +void Query_iter_valid(void); +void Query_query_optional_tag(void); +void Query_query_optional_shared_tag(void); +void Query_query_iter_10_tags(void); +void Query_query_iter_20_tags(void); +void Query_query_iter_10_components(void); +void Query_query_iter_20_components(void); + +// Testsuite 'Pairs' +void Pairs_type_w_one_pair(void); +void Pairs_type_w_two_pairs(void); +void Pairs_add_pair(void); +void Pairs_remove_pair(void); +void Pairs_add_tag_pair_for_tag(void); +void Pairs_add_tag_pair_for_component(void); +void Pairs_query_2_pairs(void); +void Pairs_query_2_pairs_2_instances_per_type(void); +void Pairs_query_pair_or_component(void); +void Pairs_query_pair_or_pair(void); +void Pairs_query_not_pair(void); +void Pairs_override_pair(void); +void Pairs_override_tag_pair(void); +void Pairs_pair_w_component_query(void); +void Pairs_on_add_pair(void); +void Pairs_on_add_pair_tag(void); +void Pairs_on_remove_pair(void); +void Pairs_on_remove_pair_tag(void); +void Pairs_on_remove_pair_on_delete(void); +void Pairs_on_remove_pair_tag_on_delete(void); +void Pairs_get_typeid_w_recycled_rel(void); +void Pairs_get_typeid_w_recycled_obj(void); +void Pairs_id_str_w_recycled_rel(void); +void Pairs_id_str_w_recycled_obj(void); +void Pairs_set_object_w_zero_sized_rel_comp(void); +void Pairs_dsl_pair(void); +void Pairs_dsl_pair_w_pred_wildcard(void); +void Pairs_dsl_pair_w_obj_wildcard(void); +void Pairs_dsl_pair_w_both_wildcard(void); +void Pairs_dsl_pair_w_explicit_subj_this(void); +void Pairs_dsl_pair_w_explicit_subj(void); +void Pairs_api_pair(void); +void Pairs_api_pair_w_pred_wildcard(void); +void Pairs_api_pair_w_obj_wildcard(void); +void Pairs_api_pair_w_both_wildcard(void); +void Pairs_api_pair_w_explicit_subj_this(void); +void Pairs_api_pair_w_explicit_subj(void); +void Pairs_typeid_from_tag(void); +void Pairs_typeid_from_component(void); +void Pairs_typeid_from_pair(void); +void Pairs_typeid_from_pair_w_rel_type(void); +void Pairs_typeid_from_pair_w_obj_type(void); +void Pairs_typeid_from_pair_w_rel_obj_type(void); +void Pairs_typeid_from_pair_w_rel_0_obj_type(void); +void Pairs_typeid_from_pair_w_rel_obj_0_type(void); +void Pairs_typeid_from_pair_w_rel_0_obj_0_type(void); +void Pairs_tag_pair_w_rel_comp(void); +void Pairs_tag_pair_w_obj_comp(void); +void Pairs_tag_pair_w_rel_obj_comp(void); +void Pairs_get_tag_pair_w_rel_comp(void); +void Pairs_get_tag_pair_w_obj_comp(void); +void Pairs_get_tag_pair_w_rel_obj_comp(void); +void Pairs_tag_pair_w_childof_w_comp(void); +void Pairs_tag_pair_w_isa_w_comp(void); +void Pairs_get_1_object(void); +void Pairs_get_1_object_not_found(void); +void Pairs_get_n_objects(void); + +// Testsuite 'Trigger' +void Trigger_on_add_trigger_before_table(void); +void Trigger_on_add_trigger_after_table(void); +void Trigger_on_remove_trigger_before_table(void); +void Trigger_on_remove_trigger_after_table(void); +void Trigger_on_add_tag(void); +void Trigger_on_add_component(void); +void Trigger_on_add_wildcard(void); +void Trigger_on_add_pair(void); +void Trigger_on_add_pair_obj_wildcard(void); +void Trigger_on_add_pair_pred_wildcard(void); +void Trigger_on_add_pair_wildcard(void); +void Trigger_on_remove_tag(void); +void Trigger_on_remove_component(void); +void Trigger_on_remove_wildcard(void); +void Trigger_on_remove_pair(void); +void Trigger_on_remove_pair_obj_wildcard(void); +void Trigger_on_remove_pair_pred_wildcard(void); +void Trigger_on_remove_pair_wildcard(void); +void Trigger_on_add_remove(void); +void Trigger_on_set_component(void); +void Trigger_on_set_wildcard(void); +void Trigger_on_set_pair(void); +void Trigger_on_set_pair_w_obj_wildcard(void); +void Trigger_on_set_pair_pred_wildcard(void); +void Trigger_on_set_pair_wildcard(void); +void Trigger_on_set_component_after_modified(void); +void Trigger_un_set_component(void); +void Trigger_un_set_wildcard(void); +void Trigger_un_set_pair(void); +void Trigger_un_set_pair_w_obj_wildcard(void); +void Trigger_un_set_pair_pred_wildcard(void); +void Trigger_un_set_pair_wildcard(void); +void Trigger_add_twice(void); +void Trigger_remove_twice(void); +void Trigger_on_remove_w_clear(void); +void Trigger_on_remove_w_delete(void); +void Trigger_on_remove_w_world_fini(void); +void Trigger_on_add_w_clone(void); +void Trigger_add_in_trigger(void); +void Trigger_remove_in_trigger(void); +void Trigger_clear_in_trigger(void); +void Trigger_delete_in_trigger(void); +void Trigger_trigger_w_named_entity(void); +void Trigger_on_remove_tree(void); +void Trigger_set_get_context(void); +void Trigger_set_get_binding_context(void); +void Trigger_trigger_w_self(void); +void Trigger_delete_trigger_w_delete_ctx(void); +void Trigger_trigger_w_index(void); + +// Testsuite 'Observer' +void Observer_2_terms_w_on_add(void); +void Observer_2_terms_w_on_remove(void); +void Observer_2_terms_w_on_set_value(void); +void Observer_2_terms_w_on_remove_value(void); +void Observer_2_terms_w_on_add_2nd(void); +void Observer_2_terms_w_on_remove_2nd(void); +void Observer_2_pair_terms_w_on_add(void); +void Observer_2_pair_terms_w_on_remove(void); +void Observer_2_wildcard_pair_terms_w_on_add(void); +void Observer_2_wildcard_pair_terms_w_on_add_2_matching(void); +void Observer_2_wildcard_pair_terms_w_on_add_3_matching(void); +void Observer_2_wildcard_pair_terms_w_on_remove(void); +void Observer_2_terms_1_not_w_on_add(void); +void Observer_2_terms_1_not_w_on_remove(void); +void Observer_2_terms_w_on_set(void); +void Observer_2_terms_w_un_set(void); +void Observer_3_terms_2_or_on_add(void); +void Observer_3_terms_2_or_on_remove(void); +void Observer_2_terms_w_from_entity_on_add(void); +void Observer_2_terms_on_remove_on_clear(void); +void Observer_2_terms_on_remove_on_delete(void); +void Observer_observer_w_self(void); +void Observer_add_after_delete_observer(void); +void Observer_remove_after_delete_observer(void); +void Observer_delete_observer_w_ctx(void); +void Observer_filter_w_strings(void); + +// Testsuite 'TriggerOnAdd' +void TriggerOnAdd_setup(void); +void TriggerOnAdd_new_match_1_of_1(void); +void TriggerOnAdd_new_match_1_of_2(void); +void TriggerOnAdd_new_no_match_1(void); +void TriggerOnAdd_new_w_count_match_1_of_1(void); +void TriggerOnAdd_add_match_1_of_1(void); +void TriggerOnAdd_add_match_1_of_2(void); +void TriggerOnAdd_add_no_match_1(void); +void TriggerOnAdd_set_match_1_of_1(void); +void TriggerOnAdd_set_no_match_1(void); +void TriggerOnAdd_clone_match_1_of_1(void); +void TriggerOnAdd_clone_match_1_of_2(void); +void TriggerOnAdd_add_again_1(void); +void TriggerOnAdd_set_again_1(void); +void TriggerOnAdd_add_again_2(void); +void TriggerOnAdd_override_after_add_in_on_add(void); +void TriggerOnAdd_add_again_in_progress(void); +void TriggerOnAdd_add_in_progress_before_system_def(void); +void TriggerOnAdd_2_systems_w_table_creation(void); +void TriggerOnAdd_2_systems_w_table_creation_in_progress(void); +void TriggerOnAdd_sys_context(void); +void TriggerOnAdd_get_sys_context_from_param(void); +void TriggerOnAdd_remove_added_component_in_on_add_w_set(void); +void TriggerOnAdd_on_add_in_on_add(void); +void TriggerOnAdd_on_remove_in_on_add(void); +void TriggerOnAdd_on_set_in_on_add(void); +void TriggerOnAdd_on_add_in_on_update(void); +void TriggerOnAdd_emplace(void); +void TriggerOnAdd_add_after_delete_trigger(void); +void TriggerOnAdd_add_after_delete_wildcard_id_trigger(void); + +// Testsuite 'TriggerOnRemove' +void TriggerOnRemove_remove_match_1_of_1(void); +void TriggerOnRemove_remove_match_1_of_2(void); +void TriggerOnRemove_remove_no_match_1(void); +void TriggerOnRemove_delete_match_1_of_1(void); +void TriggerOnRemove_delete_match_1_of_2(void); +void TriggerOnRemove_delete_no_match_1(void); +void TriggerOnRemove_remove_watched(void); +void TriggerOnRemove_delete_watched(void); +void TriggerOnRemove_valid_entity_after_delete(void); +void TriggerOnRemove_remove_after_delete_trigger(void); +void TriggerOnRemove_remove_after_delete_wildcard_id_trigger(void); +void TriggerOnRemove_has_removed_tag_trigger_1_tag(void); +void TriggerOnRemove_has_removed_tag_trigger_2_tags(void); + +// Testsuite 'TriggerOnSet' +void TriggerOnSet_set(void); +void TriggerOnSet_set_new(void); +void TriggerOnSet_set_again(void); +void TriggerOnSet_clone(void); +void TriggerOnSet_clone_w_value(void); +void TriggerOnSet_set_and_add_system(void); +void TriggerOnSet_on_set_after_override(void); +void TriggerOnSet_on_set_after_override_w_new(void); +void TriggerOnSet_on_set_after_override_w_new_w_count(void); +void TriggerOnSet_on_set_after_override_1_of_2_overridden(void); +void TriggerOnSet_on_set_after_snapshot_restore(void); +void TriggerOnSet_emplace(void); + +// Testsuite 'Monitor' +void Monitor_1_comp(void); +void Monitor_2_comps(void); +void Monitor_1_comp_1_not(void); +void Monitor_1_parent(void); +void Monitor_1_comp_1_parent(void); +void Monitor_1_comp_prefab_new(void); +void Monitor_1_comp_prefab_add(void); + +// Testsuite 'SystemOnSet' +void SystemOnSet_set_1_of_1(void); +void SystemOnSet_set_1_of_2(void); +void SystemOnSet_set_1_of_3(void); +void SystemOnSet_bulk_new_1(void); +void SystemOnSet_bulk_new_2(void); +void SystemOnSet_bulk_new_2_of_1(void); +void SystemOnSet_bulk_new_3(void); +void SystemOnSet_bulk_new_3_of_2(void); +void SystemOnSet_bulk_new_1_from_base(void); +void SystemOnSet_set_1_of_2_1_from_base(void); +void SystemOnSet_set_1_of_3_1_from_base(void); +void SystemOnSet_add_base(void); +void SystemOnSet_add_base_to_1_overridden(void); +void SystemOnSet_add_base_to_2_overridden(void); +void SystemOnSet_add_base_to_1_of_2_overridden(void); +void SystemOnSet_on_set_after_remove_override(void); +void SystemOnSet_no_set_after_remove_base(void); +void SystemOnSet_un_set_after_remove(void); +void SystemOnSet_un_set_after_remove_base(void); +void SystemOnSet_add_to_current_in_on_set(void); +void SystemOnSet_remove_from_current_in_on_set(void); +void SystemOnSet_remove_set_component_in_on_set(void); +void SystemOnSet_match_table_created_w_add_in_on_set(void); +void SystemOnSet_set_optional(void); +void SystemOnSet_set_from_nothing(void); +void SystemOnSet_add_null_type_in_on_set(void); +void SystemOnSet_add_0_entity_in_on_set(void); +void SystemOnSet_on_set_prefab(void); + +// Testsuite 'SystemUnSet' +void SystemUnSet_unset_1_of_1(void); +void SystemUnSet_unset_1_of_2(void); +void SystemUnSet_unset_1_of_3(void); +void SystemUnSet_unset_on_delete_1(void); +void SystemUnSet_unset_on_delete_2(void); +void SystemUnSet_unset_on_delete_3(void); +void SystemUnSet_unset_on_fini_1(void); +void SystemUnSet_unset_on_fini_2(void); +void SystemUnSet_unset_on_fini_3(void); +void SystemUnSet_overlapping_unset_systems(void); +void SystemUnSet_unset_move_to_nonempty_table(void); +void SystemUnSet_write_in_unset(void); + +// Testsuite 'SystemPeriodic' +void SystemPeriodic_1_type_1_component(void); +void SystemPeriodic_1_type_3_component(void); +void SystemPeriodic_3_type_1_component(void); +void SystemPeriodic_2_type_3_component(void); +void SystemPeriodic_1_type_1_component_1_tag(void); +void SystemPeriodic_2_type_1_component_1_tag(void); +void SystemPeriodic_2_type_1_and_1_not(void); +void SystemPeriodic_2_type_2_and_1_not(void); +void SystemPeriodic_2_type_2_and_2_not(void); +void SystemPeriodic_4_type_1_and_1_or(void); +void SystemPeriodic_4_type_1_and_1_or_of_3(void); +void SystemPeriodic_1_type_1_and_1_or(void); +void SystemPeriodic_2_type_1_and_1_optional(void); +void SystemPeriodic_2_type_2_and_1_optional(void); +void SystemPeriodic_6_type_1_and_2_optional(void); +void SystemPeriodic_ensure_optional_is_unset_column(void); +void SystemPeriodic_ensure_optional_is_null_shared(void); +void SystemPeriodic_match_2_systems_w_populated_table(void); +void SystemPeriodic_on_period(void); +void SystemPeriodic_on_period_long_delta(void); +void SystemPeriodic_disabled(void); +void SystemPeriodic_disabled_feature(void); +void SystemPeriodic_disabled_nested_feature(void); +void SystemPeriodic_two_refs(void); +void SystemPeriodic_filter_disabled(void); +void SystemPeriodic_match_disabled(void); +void SystemPeriodic_match_disabled_and_enabled(void); +void SystemPeriodic_match_prefab(void); +void SystemPeriodic_match_prefab_and_normal(void); +void SystemPeriodic_is_shared_on_column_not_set(void); +void SystemPeriodic_owned_column(void); +void SystemPeriodic_owned_not_column(void); +void SystemPeriodic_owned_or_column(void); +void SystemPeriodic_shared_column(void); +void SystemPeriodic_shared_not_column(void); +void SystemPeriodic_shared_or_column(void); +void SystemPeriodic_container_dont_match_inheritance(void); +void SystemPeriodic_cascade_dont_match_inheritance(void); +void SystemPeriodic_not_from_entity(void); +void SystemPeriodic_sys_context(void); +void SystemPeriodic_get_sys_context_from_param(void); +void SystemPeriodic_owned_only(void); +void SystemPeriodic_shared_only(void); +void SystemPeriodic_is_in_readonly(void); +void SystemPeriodic_get_period(void); +void SystemPeriodic_and_type(void); +void SystemPeriodic_or_type(void); + +// Testsuite 'Timer' +void Timer_timeout(void); +void Timer_interval(void); +void Timer_shared_timeout(void); +void Timer_shared_interval(void); +void Timer_start_stop_one_shot(void); +void Timer_start_stop_interval(void); +void Timer_rate_filter(void); +void Timer_rate_filter_w_rate_filter_src(void); +void Timer_rate_filter_w_timer_src(void); +void Timer_rate_filter_with_empty_src(void); +void Timer_one_shot_timer_entity(void); +void Timer_interval_timer_entity(void); +void Timer_rate_entity(void); +void Timer_nested_rate_entity(void); +void Timer_nested_rate_entity_empty_src(void); +void Timer_naked_tick_entity(void); + +// Testsuite 'SystemOnDemand' +void SystemOnDemand_enable_out_after_in(void); +void SystemOnDemand_enable_in_after_out(void); +void SystemOnDemand_enable_out_after_in_2_out_1_in(void); +void SystemOnDemand_enable_out_after_in_1_out_2_in(void); +void SystemOnDemand_enable_in_after_out_2_out_1_in(void); +void SystemOnDemand_enable_in_after_out_1_out_2_in(void); +void SystemOnDemand_disable_after_disable_in(void); +void SystemOnDemand_disable_after_disable_in_2_out_1_in(void); +void SystemOnDemand_disable_after_disable_in_1_out_2_in(void); +void SystemOnDemand_table_after_out(void); +void SystemOnDemand_table_after_out_and_in(void); +void SystemOnDemand_table_after_out_and_in_overlapping_columns(void); +void SystemOnDemand_1_out_system_2_in_systems(void); +void SystemOnDemand_1_out_system_2_in_systems_different_columns(void); +void SystemOnDemand_1_out_system_2_in_systems_overlapping_columns(void); +void SystemOnDemand_disable_after_inactive_in_system(void); +void SystemOnDemand_disable_after_2_inactive_in_systems(void); +void SystemOnDemand_disable_after_2_inactive_in_systems_different_columns(void); +void SystemOnDemand_enable_2_output_1_input_system(void); +void SystemOnDemand_enable_2_output_1_input_system_different_columns(void); +void SystemOnDemand_enable_2_output_1_input_system_overlapping_columns(void); +void SystemOnDemand_out_not_column(void); +void SystemOnDemand_trigger_on_manual(void); +void SystemOnDemand_trigger_on_manual_not_column(void); +void SystemOnDemand_on_demand_task_w_from_entity(void); +void SystemOnDemand_on_demand_task_w_not_from_entity(void); +void SystemOnDemand_enable_after_user_disable(void); + +// Testsuite 'SystemCascade' +void SystemCascade_cascade_depth_1(void); +void SystemCascade_cascade_depth_2(void); +void SystemCascade_cascade_depth_2_new_syntax(void); +void SystemCascade_add_after_match(void); +void SystemCascade_adopt_after_match(void); +void SystemCascade_rematch_w_empty_table(void); +void SystemCascade_query_w_only_cascade(void); +void SystemCascade_custom_relation_cascade_depth_1(void); +void SystemCascade_custom_relation_cascade_depth_2(void); +void SystemCascade_custom_relation_add_after_match(void); +void SystemCascade_custom_relation_adopt_after_match(void); +void SystemCascade_custom_relation_rematch_w_empty_table(void); + +// Testsuite 'SystemManual' +void SystemManual_setup(void); +void SystemManual_1_type_1_component(void); +void SystemManual_activate_status(void); +void SystemManual_no_automerge(void); +void SystemManual_dont_run_w_unmatching_entity_query(void); + +// Testsuite 'Tasks' +void Tasks_no_components(void); +void Tasks_one_tag(void); +void Tasks_from_system(void); +void Tasks_tasks_in_phases(void); + +// Testsuite 'Prefab' +void Prefab_setup(void); +void Prefab_new_w_prefab(void); +void Prefab_new_w_count_prefab(void); +void Prefab_new_w_type_w_prefab(void); +void Prefab_add_prefab(void); +void Prefab_add_type_w_prefab(void); +void Prefab_remove_prefab_after_new(void); +void Prefab_remove_prefab_after_add(void); +void Prefab_override_component(void); +void Prefab_override_remove_component(void); +void Prefab_override_2_of_3_components_1_self(void); +void Prefab_new_type_w_1_override(void); +void Prefab_new_type_w_2_overrides(void); +void Prefab_add_type_w_1_overrides(void); +void Prefab_add_type_w_2_overrides(void); +void Prefab_get_ptr_prefab(void); +void Prefab_iterate_w_prefab_shared(void); +void Prefab_match_entity_prefab_w_system_optional(void); +void Prefab_prefab_in_system_expr(void); +void Prefab_dont_match_prefab(void); +void Prefab_new_w_count_w_override(void); +void Prefab_override_2_components_different_size(void); +void Prefab_ignore_prefab_parent_component(void); +void Prefab_match_table_created_in_progress(void); +void Prefab_prefab_w_1_child(void); +void Prefab_prefab_w_2_children(void); +void Prefab_prefab_w_grandchild(void); +void Prefab_prefab_tree_1_2_1(void); +void Prefab_prefab_w_base_w_child(void); +void Prefab_prefab_w_child_w_base(void); +void Prefab_prefab_w_child_w_base_w_children(void); +void Prefab_prefab_w_child_new_w_count(void); +void Prefab_prefab_auto_override_child_component(void); +void Prefab_ignore_on_add(void); +void Prefab_ignore_on_remove(void); +void Prefab_ignore_on_set(void); +void Prefab_on_set_on_instance(void); +void Prefab_instantiate_in_progress(void); +void Prefab_copy_from_prefab_in_progress(void); +void Prefab_copy_from_prefab_first_instance_in_progress(void); +void Prefab_ref_after_realloc(void); +void Prefab_revalidate_ref_w_mixed_table_refs(void); +void Prefab_no_overwrite_on_2nd_add(void); +void Prefab_no_overwrite_on_2nd_add_in_progress(void); +void Prefab_no_instantiate_on_2nd_add(void); +void Prefab_no_instantiate_on_2nd_add_in_progress(void); +void Prefab_nested_prefab_in_progress_w_count(void); +void Prefab_nested_prefab_in_progress_w_count_set_after_override(void); +void Prefab_get_ptr_from_prefab_from_new_table_in_progress(void); +void Prefab_match_base(void); +void Prefab_match_base_after_add_in_prev_phase(void); +void Prefab_override_watched_prefab(void); +void Prefab_rematch_twice(void); +void Prefab_add_to_empty_base_in_system(void); +void Prefab_dont_inherit_disabled(void); +void Prefab_clone_after_inherit_in_on_add(void); +void Prefab_override_from_nested(void); +void Prefab_create_multiple_nested_w_on_set(void); +void Prefab_create_multiple_nested_w_on_set_in_progress(void); +void Prefab_single_on_set_on_child_w_override(void); +void Prefab_force_owned(void); +void Prefab_force_owned_2(void); +void Prefab_force_owned_nested(void); +void Prefab_force_owned_type(void); +void Prefab_force_owned_type_w_pair(void); +void Prefab_prefab_instanceof_hierarchy(void); +void Prefab_override_tag(void); +void Prefab_empty_prefab(void); +void Prefab_instanceof_0(void); +void Prefab_instantiate_empty_child_table(void); +void Prefab_instantiate_emptied_child_table(void); +void Prefab_override_2_prefabs(void); +void Prefab_rematch_after_add_instanceof_to_parent(void); +void Prefab_child_of_instance(void); +void Prefab_rematch_after_prefab_delete(void); +void Prefab_add_tag_w_low_id_to_instance(void); +void Prefab_get_type_after_base_add(void); +void Prefab_get_type_after_recycled_base_add(void); +void Prefab_new_w_recycled_base(void); +void Prefab_add_recycled_base(void); +void Prefab_remove_recycled_base(void); +void Prefab_get_from_recycled_base(void); +void Prefab_override_from_recycled_base(void); +void Prefab_remove_override_from_recycled_base(void); +void Prefab_instantiate_tree_from_recycled_base(void); +void Prefab_rematch_after_add_to_recycled_base(void); +void Prefab_get_tag_from_2nd_base(void); +void Prefab_get_component_from_2nd_base(void); +void Prefab_get_component_from_1st_base(void); +void Prefab_get_component_from_2nd_base_of_base(void); +void Prefab_get_component_from_1st_base_of_base(void); +void Prefab_get_component_from_2nd_base_prefab_base(void); +void Prefab_get_component_from_1st_base_prefab_base(void); +void Prefab_get_component_from_2nd_base_of_base_prefab_base(void); +void Prefab_get_component_from_1st_base_of_base_prefab_base(void); + +// Testsuite 'System_w_FromContainer' +void System_w_FromContainer_setup(void); +void System_w_FromContainer_1_column_from_container(void); +void System_w_FromContainer_2_column_1_from_container(void); +void System_w_FromContainer_3_column_2_from_container(void); +void System_w_FromContainer_3_column_2_from_different_container(void); +void System_w_FromContainer_2_column_1_from_container_w_not(void); +void System_w_FromContainer_2_column_1_from_container_w_not_prefab(void); +void System_w_FromContainer_3_column_1_from_comtainer_1_from_container_w_not(void); +void System_w_FromContainer_2_column_1_from_container_w_or(void); +void System_w_FromContainer_select_same_from_container(void); +void System_w_FromContainer_add_component_after_match(void); +void System_w_FromContainer_add_component_after_match_and_rematch(void); +void System_w_FromContainer_add_component_after_match_unmatch(void); +void System_w_FromContainer_add_component_after_match_unmatch_match(void); +void System_w_FromContainer_add_component_after_match_2_systems(void); +void System_w_FromContainer_add_component_in_progress_after_match(void); +void System_w_FromContainer_add_component_after_match_and_rematch_w_entity_type_expr(void); +void System_w_FromContainer_add_component_after_match_and_rematch_w_entity_type_expr_in_progress(void); +void System_w_FromContainer_adopt_after_match(void); +void System_w_FromContainer_new_child_after_match(void); +void System_w_FromContainer_realloc_after_match(void); + +// Testsuite 'System_w_FromId' +void System_w_FromId_2_column_1_from_id(void); +void System_w_FromId_3_column_2_from_id(void); +void System_w_FromId_column_type(void); + +// Testsuite 'System_w_FromSystem' +void System_w_FromSystem_2_column_1_from_system(void); +void System_w_FromSystem_3_column_2_from_system(void); +void System_w_FromSystem_auto_add_tag(void); + +// Testsuite 'System_w_FromEntity' +void System_w_FromEntity_2_column_1_from_entity(void); +void System_w_FromEntity_task_from_entity(void); +void System_w_FromEntity_task_not_from_entity(void); + +// Testsuite 'World' +void World_setup(void); +void World_progress_w_0(void); +void World_progress_w_t(void); +void World_get_tick(void); +void World_entity_range_offset(void); +void World_entity_range_offset_out_of_range(void); +void World_entity_range_limit_out_of_range(void); +void World_entity_range_add_existing_in_progress(void); +void World_entity_range_add_in_range_in_progress(void); +void World_entity_range_add_out_of_range_in_progress(void); +void World_entity_range_out_of_range_check_disabled(void); +void World_entity_range_check_after_delete(void); +void World_dim(void); +void World_dim_dim_type(void); +void World_phases(void); +void World_phases_w_merging(void); +void World_phases_match_in_create(void); +void World_measure_time(void); +void World_control_fps(void); +void World_control_fps_busy_system(void); +void World_control_fps_busy_app(void); +void World_control_fps_random_system(void); +void World_control_fps_random_app(void); +void World_measure_fps_vs_actual(void); +void World_measure_delta_time_vs_actual(void); +void World_system_time_scale(void); +void World_quit(void); +void World_get_delta_time(void); +void World_get_delta_time_auto(void); +void World_recreate_world(void); +void World_recreate_world_w_component(void); +void World_no_threading(void); +void World_no_time(void); +void World_is_entity_enabled(void); +void World_get_stats(void); +void World_ensure_empty_root(void); +void World_register_alias_twice_same_entity(void); +void World_register_alias_twice_different_entity(void); + +// Testsuite 'Type' +void Type_setup(void); +void Type_type_of_1_tostr(void); +void Type_type_of_2_tostr(void); +void Type_type_of_2_tostr_no_id(void); +void Type_type_redefine(void); +void Type_type_has(void); +void Type_type_has_not(void); +void Type_zero_type_has_not(void); +void Type_type_merge(void); +void Type_type_merge_overlap(void); +void Type_type_merge_overlap_one(void); +void Type_type_add(void); +void Type_type_add_empty(void); +void Type_type_add_entity_again(void); +void Type_type_add_out_of_order(void); +void Type_type_add_existing(void); +void Type_type_add_empty_existing(void); +void Type_type_add_out_of_order_existing(void); +void Type_type_remove(void); +void Type_type_remove_empty(void); +void Type_type_remove_non_existing(void); +void Type_type_of_2_add(void); +void Type_type_of_3_add_entity_again(void); +void Type_invalid_entity_type_expression(void); +void Type_invalid_container_type_expression(void); +void Type_invalid_system_type_expression(void); +void Type_type_from_empty_entity(void); +void Type_get_type(void); +void Type_get_type_from_empty(void); +void Type_get_type_from_0(void); +void Type_entity_from_type(void); +void Type_entity_from_empty_type(void); +void Type_entity_from_type_w_2_elements(void); +void Type_type_from_entity(void); +void Type_type_from_empty(void); +void Type_type_from_0(void); +void Type_type_to_expr_1_comp(void); +void Type_type_to_expr_2_comp(void); +void Type_type_to_expr_instanceof(void); +void Type_type_to_expr_childof(void); +void Type_type_to_expr_pair(void); +void Type_type_to_expr_pair_w_comp(void); +void Type_type_to_expr_scope(void); +void Type_type_from_expr(void); +void Type_type_from_expr_scope(void); +void Type_type_from_expr_digit(void); +void Type_type_from_expr_instanceof(void); +void Type_type_from_expr_childof(void); +void Type_type_from_expr_pair(void); +void Type_type_from_expr_pair_w_comp(void); +void Type_entity_str(void); +void Type_entity_path_str(void); +void Type_entity_instanceof_str(void); +void Type_entity_childof_str(void); +void Type_entity_pair_str(void); +void Type_entity_switch_str(void); +void Type_entity_case_str(void); +void Type_entity_and_str(void); +void Type_entity_or_str(void); +void Type_entity_xor_str(void); +void Type_entity_not_str(void); +void Type_entity_str_small_buffer(void); +void Type_role_pair_str(void); +void Type_role_switch_str(void); +void Type_role_case_str(void); +void Type_role_and_str(void); +void Type_role_or_str(void); +void Type_role_xor_str(void); +void Type_role_not_str(void); +void Type_role_owned_str(void); +void Type_role_disabled_str(void); +void Type_large_type_expr(void); +void Type_large_type_expr_limit(void); + +// Testsuite 'Run' +void Run_setup(void); +void Run_run(void); +void Run_run_w_param(void); +void Run_run_no_match(void); +void Run_run_w_offset(void); +void Run_run_w_offset_skip_1_archetype(void); +void Run_run_w_offset_skip_1_archetype_plus_one(void); +void Run_run_w_offset_skip_2_archetypes(void); +void Run_run_w_limit_skip_1_archetype(void); +void Run_run_w_limit_skip_1_archetype_minus_one(void); +void Run_run_w_limit_skip_2_archetypes(void); +void Run_run_w_offset_1_limit_max(void); +void Run_run_w_offset_1_limit_minus_1(void); +void Run_run_w_offset_2_type_limit_max(void); +void Run_run_w_offset_2_type_limit_minus_1(void); +void Run_run_w_limit_1_all_offsets(void); +void Run_run_w_offset_out_of_bounds(void); +void Run_run_w_limit_out_of_bounds(void); +void Run_run_w_component_filter(void); +void Run_run_w_type_filter_of_2(void); +void Run_run_w_container_filter(void); +void Run_run_comb_10_entities_1_type(void); +void Run_run_comb_10_entities_2_types(void); +void Run_run_w_interrupt(void); +void Run_run_staging(void); + +// Testsuite 'MultiThread' +void MultiThread_setup(void); +void MultiThread_2_thread_1_entity(void); +void MultiThread_2_thread_2_entity(void); +void MultiThread_2_thread_5_entity(void); +void MultiThread_2_thread_10_entity(void); +void MultiThread_3_thread_1_entity(void); +void MultiThread_3_thread_2_entity(void); +void MultiThread_3_thread_5_entity(void); +void MultiThread_3_thread_10_entity(void); +void MultiThread_4_thread_1_entity(void); +void MultiThread_4_thread_2_entity(void); +void MultiThread_4_thread_5_entity(void); +void MultiThread_4_thread_10_entity(void); +void MultiThread_5_thread_1_entity(void); +void MultiThread_5_thread_2_entity(void); +void MultiThread_5_thread_5_entity(void); +void MultiThread_5_thread_10_entity(void); +void MultiThread_6_thread_1_entity(void); +void MultiThread_6_thread_2_entity(void); +void MultiThread_6_thread_5_entity(void); +void MultiThread_6_thread_10_entity(void); +void MultiThread_2_thread_test_combs_100_entity_w_next_worker(void); +void MultiThread_2_thread_test_combs_100_entity(void); +void MultiThread_3_thread_test_combs_100_entity(void); +void MultiThread_4_thread_test_combs_100_entity(void); +void MultiThread_5_thread_test_combs_100_entity(void); +void MultiThread_6_thread_test_combs_100_entity(void); +void MultiThread_2_thread_test_combs_100_entity_2_types(void); +void MultiThread_3_thread_test_combs_100_entity_2_types(void); +void MultiThread_4_thread_test_combs_100_entity_2_types(void); +void MultiThread_5_thread_test_combs_100_entity_2_types(void); +void MultiThread_6_thread_test_combs_100_entity_2_types(void); +void MultiThread_change_thread_count(void); +void MultiThread_multithread_quit(void); +void MultiThread_schedule_w_tasks(void); +void MultiThread_reactive_system(void); +void MultiThread_fini_after_set_threads(void); + +// Testsuite 'DeferredActions' +void DeferredActions_defer_new(void); +void DeferredActions_defer_bulk_new(void); +void DeferredActions_defer_bulk_new_w_data(void); +void DeferredActions_defer_bulk_new_w_data_pair(void); +void DeferredActions_defer_bulk_new_two(void); +void DeferredActions_defer_bulk_new_w_data_two(void); +void DeferredActions_defer_add(void); +void DeferredActions_defer_add_two(void); +void DeferredActions_defer_remove(void); +void DeferredActions_defer_remove_two(void); +void DeferredActions_defer_set(void); +void DeferredActions_defer_delete(void); +void DeferredActions_defer_twice(void); +void DeferredActions_defer_twice_in_progress(void); +void DeferredActions_run_w_defer(void); +void DeferredActions_system_in_progress_w_defer(void); +void DeferredActions_defer_get_mut_no_modify(void); +void DeferredActions_defer_get_mut_w_modify(void); +void DeferredActions_defer_modify(void); +void DeferredActions_defer_set_pair(void); +void DeferredActions_defer_clear(void); +void DeferredActions_defer_add_after_delete(void); +void DeferredActions_defer_set_after_delete(void); +void DeferredActions_defer_get_mut_after_delete(void); +void DeferredActions_defer_get_mut_after_delete_2nd_to_last(void); +void DeferredActions_defer_add_child_to_deleted_parent(void); +void DeferredActions_recreate_deleted_entity_while_deferred(void); +void DeferredActions_defer_add_to_recycled_id(void); +void DeferredActions_defer_add_to_recycled_id_w_role(void); +void DeferredActions_defer_add_to_recycled_relation(void); +void DeferredActions_defer_add_to_recycled_object(void); +void DeferredActions_defer_add_to_recycled_object_childof(void); +void DeferredActions_defer_add_to_deleted_id(void); +void DeferredActions_defer_add_to_deleted_id_w_role(void); +void DeferredActions_defer_add_to_deleted_relation(void); +void DeferredActions_defer_add_to_deleted_object(void); +void DeferredActions_defer_add_to_deleted_object_childof(void); +void DeferredActions_defer_delete_added_id(void); +void DeferredActions_defer_delete_added_id_w_role(void); +void DeferredActions_defer_delete_added_relation(void); +void DeferredActions_defer_delete_added_object(void); +void DeferredActions_defer_delete_added_object_childof(void); +void DeferredActions_discard_add(void); +void DeferredActions_discard_remove(void); +void DeferredActions_discard_add_two(void); +void DeferredActions_discard_remove_two(void); +void DeferredActions_discard_child(void); +void DeferredActions_discard_child_w_add(void); +void DeferredActions_defer_return_value(void); +void DeferredActions_defer_get_mut_pair(void); +void DeferredActions_async_stage_add(void); +void DeferredActions_async_stage_add_twice(void); +void DeferredActions_async_stage_remove(void); +void DeferredActions_async_stage_clear(void); +void DeferredActions_async_stage_delete(void); +void DeferredActions_async_stage_new(void); +void DeferredActions_async_stage_no_get(void); +void DeferredActions_async_stage_readonly(void); +void DeferredActions_async_stage_is_async(void); +void DeferredActions_register_component_while_in_progress(void); +void DeferredActions_register_component_while_staged(void); +void DeferredActions_register_component_while_deferred(void); +void DeferredActions_defer_enable(void); +void DeferredActions_defer_disable(void); + +// Testsuite 'SingleThreadStaging' +void SingleThreadStaging_setup(void); +void SingleThreadStaging_new_empty(void); +void SingleThreadStaging_new_w_component(void); +void SingleThreadStaging_new_w_type_of_2(void); +void SingleThreadStaging_new_empty_w_count(void); +void SingleThreadStaging_new_component_w_count(void); +void SingleThreadStaging_new_type_w_count(void); +void SingleThreadStaging_add_to_new_empty(void); +void SingleThreadStaging_2_add_to_new_empty(void); +void SingleThreadStaging_add_remove_same_to_new_empty(void); +void SingleThreadStaging_add_remove_2_same_to_new_empty(void); +void SingleThreadStaging_add_remove_same_to_new_w_component(void); +void SingleThreadStaging_2_add_1_remove_to_new_empty(void); +void SingleThreadStaging_2_add_1_remove_same_to_new_empty(void); +void SingleThreadStaging_clone(void); +void SingleThreadStaging_clone_w_value(void); +void SingleThreadStaging_add_to_current(void); +void SingleThreadStaging_2_add_to_current(void); +void SingleThreadStaging_remove_from_current(void); +void SingleThreadStaging_remove_2_from_current(void); +void SingleThreadStaging_add_remove_same_to_current(void); +void SingleThreadStaging_add_remove_same_existing_to_current(void); +void SingleThreadStaging_remove_add_same_to_current(void); +void SingleThreadStaging_remove_add_same_existing_to_current(void); +void SingleThreadStaging_add_remove_2_same_to_current(void); +void SingleThreadStaging_add_remove_2_same_existing_to_current(void); +void SingleThreadStaging_remove_add_2_same_to_current(void); +void SingleThreadStaging_remove_add_2_same_existing_to_current(void); +void SingleThreadStaging_add_remove_different_to_current(void); +void SingleThreadStaging_add_remove_add_same_to_current(void); +void SingleThreadStaging_2_add_1_remove_to_current(void); +void SingleThreadStaging_1_add_2_remove_to_current(void); +void SingleThreadStaging_delete_current(void); +void SingleThreadStaging_delete_even(void); +void SingleThreadStaging_delete_new_empty(void); +void SingleThreadStaging_delete_new_w_component(void); +void SingleThreadStaging_set_current(void); +void SingleThreadStaging_set_new_empty(void); +void SingleThreadStaging_set_new_w_component(void); +void SingleThreadStaging_set_existing_new_w_component(void); +void SingleThreadStaging_set_new_after_add(void); +void SingleThreadStaging_remove_after_set(void); +void SingleThreadStaging_delete_after_set(void); +void SingleThreadStaging_add_to_current_in_on_add(void); +void SingleThreadStaging_remove_from_current_in_on_add(void); +void SingleThreadStaging_remove_added_component_in_on_add(void); +void SingleThreadStaging_match_table_created_in_progress(void); +void SingleThreadStaging_match_table_created_w_new_in_progress(void); +void SingleThreadStaging_match_table_created_w_new_in_on_set(void); +void SingleThreadStaging_merge_table_w_container_added_in_progress(void); +void SingleThreadStaging_merge_table_w_container_added_on_set(void); +void SingleThreadStaging_merge_table_w_container_added_on_set_reverse(void); +void SingleThreadStaging_merge_after_tasks(void); +void SingleThreadStaging_override_after_remove_in_progress(void); +void SingleThreadStaging_get_parent_in_progress(void); +void SingleThreadStaging_merge_once(void); +void SingleThreadStaging_clear_stage_after_merge(void); +void SingleThreadStaging_get_mutable(void); +void SingleThreadStaging_get_mutable_from_main(void); +void SingleThreadStaging_get_mutable_w_add(void); +void SingleThreadStaging_on_add_after_new_type_in_progress(void); +void SingleThreadStaging_new_type_from_entity(void); +void SingleThreadStaging_existing_type_from_entity(void); +void SingleThreadStaging_new_type_add(void); +void SingleThreadStaging_existing_type_add(void); +void SingleThreadStaging_lock_table(void); +void SingleThreadStaging_recursive_lock_table(void); +void SingleThreadStaging_modify_after_lock(void); +void SingleThreadStaging_get_empty_case_from_stage(void); +void SingleThreadStaging_get_case_from_stage(void); +void SingleThreadStaging_get_object_from_stage(void); + +// Testsuite 'MultiThreadStaging' +void MultiThreadStaging_setup(void); +void MultiThreadStaging_2_threads_add_to_current(void); +void MultiThreadStaging_3_threads_add_to_current(void); +void MultiThreadStaging_4_threads_add_to_current(void); +void MultiThreadStaging_5_threads_add_to_current(void); +void MultiThreadStaging_6_threads_add_to_current(void); +void MultiThreadStaging_2_threads_on_add(void); +void MultiThreadStaging_new_w_count(void); +void MultiThreadStaging_custom_thread_auto_merge(void); +void MultiThreadStaging_custom_thread_manual_merge(void); +void MultiThreadStaging_custom_thread_partial_manual_merge(void); + +// Testsuite 'Stresstests' +void Stresstests_setup(void); +void Stresstests_create_1m_set_two_components(void); +void Stresstests_create_delete_entity_random_components(void); +void Stresstests_set_entity_random_components(void); +void Stresstests_create_delete_entity_random_components_staged(void); +void Stresstests_set_entity_random_components_staged(void); +void Stresstests_create_delete_entity_random_components_2_threads(void); +void Stresstests_set_entity_random_components_2_threads(void); +void Stresstests_create_delete_entity_random_components_6_threads(void); +void Stresstests_set_entity_random_components_6_threads(void); +void Stresstests_create_delete_entity_random_components_12_threads(void); +void Stresstests_set_entity_random_components_12_threads(void); +void Stresstests_create_2m_entities_1_comp(void); +void Stresstests_create_2m_entities_bulk_1_comp(void); +void Stresstests_add_1k_tags(void); +void Stresstests_create_1m_set_two_components(void); + +// Testsuite 'Snapshot' +void Snapshot_simple_snapshot(void); +void Snapshot_snapshot_after_new(void); +void Snapshot_snapshot_after_delete(void); +void Snapshot_snapshot_after_new_type(void); +void Snapshot_snapshot_after_add(void); +void Snapshot_snapshot_after_remove(void); +void Snapshot_snapshot_w_include_filter(void); +void Snapshot_snapshot_w_exclude_filter(void); +void Snapshot_snapshot_w_filter_after_new(void); +void Snapshot_snapshot_w_filter_after_delete(void); +void Snapshot_snapshot_free_empty(void); +void Snapshot_snapshot_free(void); +void Snapshot_snapshot_free_filtered(void); +void Snapshot_snapshot_free_filtered_w_dtor(void); +void Snapshot_snapshot_activate_table_w_filter(void); +void Snapshot_snapshot_copy(void); +void Snapshot_snapshot_get_ref_after_restore(void); +void Snapshot_new_after_snapshot(void); +void Snapshot_new_empty_after_snapshot(void); +void Snapshot_add_after_snapshot(void); +void Snapshot_delete_after_snapshot(void); +void Snapshot_set_after_snapshot(void); +void Snapshot_restore_recycled(void); +void Snapshot_snapshot_w_new_in_onset(void); +void Snapshot_snapshot_w_new_in_onset_in_snapshot_table(void); +void Snapshot_snapshot_from_stage(void); + +// Testsuite 'Modules' +void Modules_setup(void); +void Modules_simple_module(void); +void Modules_import_module_from_system(void); +void Modules_import_again(void); +void Modules_scoped_component(void); +void Modules_scoped_tag(void); +void Modules_scoped_system(void); +void Modules_scoped_entity(void); +void Modules_name_prefix_component(void); +void Modules_name_prefix_tag(void); +void Modules_name_prefix_system(void); +void Modules_name_prefix_entity(void); +void Modules_name_prefix_type(void); +void Modules_name_prefix_prefab(void); +void Modules_name_prefix_pipeline(void); +void Modules_name_prefix_trigger(void); +void Modules_name_prefix_underscore(void); +void Modules_lookup_by_symbol(void); +void Modules_import_type(void); +void Modules_nested_module(void); +void Modules_module_tag_on_namespace(void); + +// Testsuite 'DirectAccess' +void DirectAccess_get_table_from_str(void); +void DirectAccess_get_table_from_type(void); +void DirectAccess_insert_record(void); +void DirectAccess_insert_record_w_entity(void); +void DirectAccess_table_count(void); +void DirectAccess_find_column(void); +void DirectAccess_get_column(void); +void DirectAccess_get_empty_column(void); +void DirectAccess_set_column(void); +void DirectAccess_delete_column(void); +void DirectAccess_delete_column_explicit(void); +void DirectAccess_delete_column_w_dtor(void); +void DirectAccess_copy_to(void); +void DirectAccess_copy_pod_to(void); +void DirectAccess_move_to(void); +void DirectAccess_copy_to_no_copy(void); +void DirectAccess_move_to_no_move(void); +void DirectAccess_find_record_not_exists(void); +void DirectAccess_get_entities_empty_table(void); +void DirectAccess_get_records_empty_table(void); +void DirectAccess_get_column_empty_table(void); +void DirectAccess_delete_column_empty_table(void); +void DirectAccess_get_record_column_empty_table(void); +void DirectAccess_has_module(void); + +// Testsuite 'Internals' +void Internals_setup(void); +void Internals_deactivate_table(void); +void Internals_activate_table(void); +void Internals_activate_deactivate_table(void); +void Internals_activate_deactivate_reactive(void); +void Internals_activate_deactivate_activate_other(void); +void Internals_no_double_system_table_after_merge(void); +void Internals_recreate_deleted_table(void); +void Internals_create_65k_tables(void); + +// Testsuite 'Error' +void Error_setup(void); +void Error_abort(void); +void Error_abort_w_param(void); +void Error_override_abort(void); +void Error_assert_true(void); +void Error_assert_false(void); +void Error_assert_false_w_param(void); +void Error_error_codes(void); +void Error_log_dbg(void); +void Error_log_log(void); +void Error_log_warning(void); +void Error_log_error(void); + +bake_test_case Entity_testcases[] = { + { + "init_id", + Entity_init_id + }, + { + "init_id_name", + Entity_init_id_name + }, + { + "init_id_path", + Entity_init_id_path + }, + { + "init_id_add_1_comp", + Entity_init_id_add_1_comp + }, + { + "init_id_add_2_comp", + Entity_init_id_add_2_comp + }, + { + "init_id_w_scope", + Entity_init_id_w_scope + }, + { + "init_id_name_w_scope", + Entity_init_id_name_w_scope + }, + { + "init_id_path_w_scope", + Entity_init_id_path_w_scope + }, + { + "init_id_name_1_comp", + Entity_init_id_name_1_comp + }, + { + "init_id_name_2_comp", + Entity_init_id_name_2_comp + }, + { + "init_id_name_2_comp_w_scope", + Entity_init_id_name_2_comp_w_scope + }, + { + "id_add_1_comp", + Entity_id_add_1_comp + }, + { + "id_add_2_comp", + Entity_id_add_2_comp + }, + { + "id_remove_1_comp", + Entity_id_remove_1_comp + }, + { + "id_remove_2_comp", + Entity_id_remove_2_comp + }, + { + "init_id_path_w_sep", + Entity_init_id_path_w_sep + }, + { + "find_id_name", + Entity_find_id_name + }, + { + "find_w_existing_id_name", + Entity_find_w_existing_id_name + }, + { + "find_id_name_w_scope", + Entity_find_id_name_w_scope + }, + { + "find_id_path", + Entity_find_id_path + }, + { + "find_id_path_w_scope", + Entity_find_id_path_w_scope + }, + { + "find_id_name_match", + Entity_find_id_name_match + }, + { + "find_id_name_match_w_scope", + Entity_find_id_name_match_w_scope + }, + { + "find_id_path_match", + Entity_find_id_path_match + }, + { + "find_id_path_match_w_scope", + Entity_find_id_path_match_w_scope + }, + { + "find_id_name_mismatch", + Entity_find_id_name_mismatch + }, + { + "find_id_name_mismatch_w_scope", + Entity_find_id_name_mismatch_w_scope + }, + { + "find_id_path_mismatch", + Entity_find_id_path_mismatch + }, + { + "find_id_path_mismatch_w_scope", + Entity_find_id_path_mismatch_w_scope + }, + { + "find_id_add_1_comp", + Entity_find_id_add_1_comp + }, + { + "find_id_add_2_comp", + Entity_find_id_add_2_comp + }, + { + "find_id_remove_1_comp", + Entity_find_id_remove_1_comp + }, + { + "find_id_remove_2_comp", + Entity_find_id_remove_2_comp + }, + { + "init_w_scope_name", + Entity_init_w_scope_name + }, + { + "init_w_core_name", + Entity_init_w_core_name + }, + { + "init_w_with", + Entity_init_w_with + }, + { + "init_w_with_w_name", + Entity_init_w_with_w_name + }, + { + "init_w_with_w_scope", + Entity_init_w_with_w_scope + }, + { + "init_w_with_w_name_scope", + Entity_init_w_with_w_name_scope + }, + { + "is_valid", + Entity_is_valid + }, + { + "is_recycled_valid", + Entity_is_recycled_valid + }, + { + "is_0_valid", + Entity_is_0_valid + }, + { + "is_junk_valid", + Entity_is_junk_valid + }, + { + "is_not_alive_valid", + Entity_is_not_alive_valid + } +}; + +bake_test_case New_testcases[] = { + { + "empty", + New_empty + }, + { + "component", + New_component + }, + { + "type", + New_type + }, + { + "type_of_2", + New_type_of_2 + }, + { + "type_w_type", + New_type_w_type + }, + { + "type_w_2_types", + New_type_w_2_types + }, + { + "type_mixed", + New_type_mixed + }, + { + "tag", + New_tag + }, + { + "type_w_tag", + New_type_w_tag + }, + { + "type_w_2_tags", + New_type_w_2_tags + }, + { + "type_w_tag_mixed", + New_type_w_tag_mixed + }, + { + "redefine_component", + New_redefine_component + }, + { + "recycle_id_empty", + New_recycle_id_empty + }, + { + "recycle_id_w_entity", + New_recycle_id_w_entity + }, + { + "recycle_id_w_type", + New_recycle_id_w_type + }, + { + "recycle_empty_staged_delete", + New_recycle_empty_staged_delete + }, + { + "recycle_staged_delete", + New_recycle_staged_delete + }, + { + "new_id", + New_new_id + }, + { + "new_component_id", + New_new_component_id + }, + { + "new_hi_component_id", + New_new_hi_component_id + }, + { + "new_component_id_skip_used", + New_new_component_id_skip_used + }, + { + "new_component_id_skip_to_hi_id", + New_new_component_id_skip_to_hi_id + }, + { + "new_w_entity_0", + New_new_w_entity_0 + }, + { + "create_w_explicit_id_2_worlds", + New_create_w_explicit_id_2_worlds + }, + { + "new_w_id_0_w_with", + New_new_w_id_0_w_with + }, + { + "new_w_id_w_with", + New_new_w_id_w_with + }, + { + "new_w_type_0_w_with", + New_new_w_type_0_w_with + }, + { + "new_w_type_w_with", + New_new_w_type_w_with + }, + { + "new_w_id_w_with_w_scope", + New_new_w_id_w_with_w_scope + }, + { + "new_w_type_w_with_w_scope", + New_new_w_type_w_with_w_scope + }, + { + "new_w_id_w_with_defer", + New_new_w_id_w_with_defer + }, + { + "new_w_id_w_with_defer_w_scope", + New_new_w_id_w_with_defer_w_scope + }, + { + "new_w_type_w_with_defer", + New_new_w_type_w_with_defer + }, + { + "new_w_type_w_with_defer_w_scope", + New_new_w_type_w_with_defer_w_scope + } +}; + +bake_test_case New_w_Count_testcases[] = { + { + "empty", + New_w_Count_empty + }, + { + "component", + New_w_Count_component + }, + { + "type", + New_w_Count_type + }, + { + "type_of_2", + New_w_Count_type_of_2 + }, + { + "type_w_type", + New_w_Count_type_w_type + }, + { + "type_w_2_types", + New_w_Count_type_w_2_types + }, + { + "type_mixed", + New_w_Count_type_mixed + }, + { + "tag", + New_w_Count_tag + }, + { + "type_w_tag", + New_w_Count_type_w_tag + }, + { + "type_w_2_tags", + New_w_Count_type_w_2_tags + }, + { + "type_w_tag_mixed", + New_w_Count_type_w_tag_mixed + }, + { + "new_w_data_1_comp", + New_w_Count_new_w_data_1_comp + }, + { + "new_w_data_2_comp", + New_w_Count_new_w_data_2_comp + }, + { + "new_w_data_w_tag", + New_w_Count_new_w_data_w_tag + }, + { + "new_w_data_w_comp_and_tag", + New_w_Count_new_w_data_w_comp_and_tag + }, + { + "new_w_data_pair", + New_w_Count_new_w_data_pair + }, + { + "new_w_data_pair", + New_w_Count_new_w_data_pair + }, + { + "new_w_data_2_comp_1_not_set", + New_w_Count_new_w_data_2_comp_1_not_set + }, + { + "new_w_on_add_on_set_monitor", + New_w_Count_new_w_on_add_on_set_monitor + }, + { + "new_w_data_override_set_comp", + New_w_Count_new_w_data_override_set_comp + }, + { + "new_w_data_override_set_pair", + New_w_Count_new_w_data_override_set_pair + } +}; + +bake_test_case Add_testcases[] = { + { + "zero", + Add_zero + }, + { + "component", + Add_component + }, + { + "component_again", + Add_component_again + }, + { + "2_components", + Add_2_components + }, + { + "2_components_again", + Add_2_components_again + }, + { + "2_components_overlap", + Add_2_components_overlap + }, + { + "type", + Add_type + }, + { + "type_of_2", + Add_type_of_2 + }, + { + "type_w_type", + Add_type_w_type + }, + { + "type_w_2_types", + Add_type_w_2_types + }, + { + "type_mixed", + Add_type_mixed + }, + { + "type_again", + Add_type_again + }, + { + "type_overlap", + Add_type_overlap + }, + { + "type_again_component", + Add_type_again_component + }, + { + "type_overlap_component", + Add_type_overlap_component + }, + { + "component_to_nonempty", + Add_component_to_nonempty + }, + { + "component_to_nonempty_again", + Add_component_to_nonempty_again + }, + { + "component_to_nonempty_overlap", + Add_component_to_nonempty_overlap + }, + { + "type_to_nonempty", + Add_type_to_nonempty + }, + { + "type_to_nonempty_again", + Add_type_to_nonempty_again + }, + { + "type_to_nonempty_overlap", + Add_type_to_nonempty_overlap + }, + { + "tag", + Add_tag + }, + { + "type_w_tag", + Add_type_w_tag + }, + { + "type_w_2_tags", + Add_type_w_2_tags + }, + { + "type_w_tag_mixed", + Add_type_w_tag_mixed + }, + { + "add_remove", + Add_add_remove + }, + { + "add_remove_same", + Add_add_remove_same + }, + { + "add_remove_entity", + Add_add_remove_entity + }, + { + "add_remove_entity_same", + Add_add_remove_entity_same + }, + { + "add_2_remove", + Add_add_2_remove + }, + { + "add_entity", + Add_add_entity + }, + { + "remove_entity", + Add_remove_entity + }, + { + "add_0_entity", + Add_add_0_entity + }, + { + "remove_0_entity", + Add_remove_0_entity + }, + { + "add_w_xor", + Add_add_w_xor + }, + { + "add_same_w_xor", + Add_add_same_w_xor + }, + { + "add_after_remove_xor", + Add_add_after_remove_xor + } +}; + +bake_test_case Switch_testcases[] = { + { + "get_case_empty", + Switch_get_case_empty + }, + { + "get_case_no_switch", + Switch_get_case_no_switch + }, + { + "get_case_unset", + Switch_get_case_unset + }, + { + "get_case_set", + Switch_get_case_set + }, + { + "get_case_change", + Switch_get_case_change + }, + { + "remove_case", + Switch_remove_case + }, + { + "remove_last", + Switch_remove_last + }, + { + "delete_first", + Switch_delete_first + }, + { + "delete_last", + Switch_delete_last + }, + { + "delete_first_last", + Switch_delete_first_last + }, + { + "3_entities_same_case", + Switch_3_entities_same_case + }, + { + "2_entities_1_change_case", + Switch_2_entities_1_change_case + }, + { + "3_entities_change_case", + Switch_3_entities_change_case + }, + { + "query_switch", + Switch_query_switch + }, + { + "query_1_case_1_type", + Switch_query_1_case_1_type + }, + { + "query_1_case_2_types", + Switch_query_1_case_2_types + }, + { + "query_2_cases_1_type", + Switch_query_2_cases_1_type + }, + { + "query_2_cases_2_types", + Switch_query_2_cases_2_types + }, + { + "query_after_remove", + Switch_query_after_remove + }, + { + "add_switch_in_stage", + Switch_add_switch_in_stage + }, + { + "add_case_in_stage", + Switch_add_case_in_stage + }, + { + "change_case_in_stage", + Switch_change_case_in_stage + }, + { + "change_one_case_in_stage", + Switch_change_one_case_in_stage + }, + { + "remove_switch_in_stage", + Switch_remove_switch_in_stage + }, + { + "switch_no_match_for_case", + Switch_switch_no_match_for_case + }, + { + "empty_entity_has_case", + Switch_empty_entity_has_case + }, + { + "zero_entity_has_case", + Switch_zero_entity_has_case + }, + { + "add_to_entity_w_switch", + Switch_add_to_entity_w_switch + }, + { + "add_pair_to_entity_w_switch", + Switch_add_pair_to_entity_w_switch + }, + { + "sort", + Switch_sort + }, + { + "recycled_tags", + Switch_recycled_tags + }, + { + "query_recycled_tags", + Switch_query_recycled_tags + }, + { + "single_case", + Switch_single_case + } +}; + +bake_test_case EnabledComponents_testcases[] = { + { + "is_component_enabled", + EnabledComponents_is_component_enabled + }, + { + "is_empty_entity_disabled", + EnabledComponents_is_empty_entity_disabled + }, + { + "is_0_entity_disabled", + EnabledComponents_is_0_entity_disabled + }, + { + "is_0_component_disabled", + EnabledComponents_is_0_component_disabled + }, + { + "is_nonexist_component_disabled", + EnabledComponents_is_nonexist_component_disabled + }, + { + "is_enabled_component_enabled", + EnabledComponents_is_enabled_component_enabled + }, + { + "is_disabled_component_enabled", + EnabledComponents_is_disabled_component_enabled + }, + { + "has_enabled_component", + EnabledComponents_has_enabled_component + }, + { + "is_enabled_after_add", + EnabledComponents_is_enabled_after_add + }, + { + "is_enabled_after_remove", + EnabledComponents_is_enabled_after_remove + }, + { + "is_enabled_after_disable", + EnabledComponents_is_enabled_after_disable + }, + { + "is_disabled_after_enable", + EnabledComponents_is_disabled_after_enable + }, + { + "is_enabled_randomized", + EnabledComponents_is_enabled_randomized + }, + { + "is_enabled_after_add_randomized", + EnabledComponents_is_enabled_after_add_randomized + }, + { + "is_enabled_after_randomized_add_randomized", + EnabledComponents_is_enabled_after_randomized_add_randomized + }, + { + "is_enabled_2", + EnabledComponents_is_enabled_2 + }, + { + "is_enabled_3", + EnabledComponents_is_enabled_3 + }, + { + "is_enabled_2_after_add", + EnabledComponents_is_enabled_2_after_add + }, + { + "is_enabled_3_after_add", + EnabledComponents_is_enabled_3_after_add + }, + { + "query_disabled", + EnabledComponents_query_disabled + }, + { + "query_disabled_skip_initial", + EnabledComponents_query_disabled_skip_initial + }, + { + "query_mod_2", + EnabledComponents_query_mod_2 + }, + { + "query_mod_8", + EnabledComponents_query_mod_8 + }, + { + "query_mod_64", + EnabledComponents_query_mod_64 + }, + { + "query_mod_256", + EnabledComponents_query_mod_256 + }, + { + "query_mod_1024", + EnabledComponents_query_mod_1024 + }, + { + "query_enable_mod_10", + EnabledComponents_query_enable_mod_10 + }, + { + "query_mod_2_2_bitsets", + EnabledComponents_query_mod_2_2_bitsets + }, + { + "query_mod_8_2_bitsets", + EnabledComponents_query_mod_8_2_bitsets + }, + { + "query_mod_64_2_bitsets", + EnabledComponents_query_mod_64_2_bitsets + }, + { + "query_mod_256_2_bitsets", + EnabledComponents_query_mod_256_2_bitsets + }, + { + "query_mod_1024_2_bitsets", + EnabledComponents_query_mod_1024_2_bitsets + }, + { + "query_randomized_2_bitsets", + EnabledComponents_query_randomized_2_bitsets + }, + { + "query_randomized_3_bitsets", + EnabledComponents_query_randomized_3_bitsets + }, + { + "query_randomized_4_bitsets", + EnabledComponents_query_randomized_4_bitsets + }, + { + "defer_enable", + EnabledComponents_defer_enable + }, + { + "sort", + EnabledComponents_sort + } +}; + +bake_test_case Remove_testcases[] = { + { + "zero", + Remove_zero + }, + { + "zero_from_nonzero", + Remove_zero_from_nonzero + }, + { + "1_of_1", + Remove_1_of_1 + }, + { + "1_of_1_again", + Remove_1_of_1_again + }, + { + "1_of_2", + Remove_1_of_2 + }, + { + "2_of_2", + Remove_2_of_2 + }, + { + "2_of_3", + Remove_2_of_3 + }, + { + "2_again", + Remove_2_again + }, + { + "2_overlap", + Remove_2_overlap + }, + { + "type_of_1_of_2", + Remove_type_of_1_of_2 + }, + { + "type_of_2_of_2", + Remove_type_of_2_of_2 + }, + { + "type_of_2_of_3", + Remove_type_of_2_of_3 + }, + { + "1_from_empty", + Remove_1_from_empty + }, + { + "type_from_empty", + Remove_type_from_empty + }, + { + "not_added", + Remove_not_added + } +}; + +bake_test_case Parser_testcases[] = { + { + "resolve_this", + Parser_resolve_this + }, + { + "resolve_wildcard", + Parser_resolve_wildcard + }, + { + "resolve_is_a", + Parser_resolve_is_a + }, + { + "0", + Parser_0 + }, + { + "component_implicit_subject", + Parser_component_implicit_subject + }, + { + "component_explicit_subject", + Parser_component_explicit_subject + }, + { + "component_explicit_subject_this", + Parser_component_explicit_subject_this + }, + { + "component_explicit_subject_this_by_name", + Parser_component_explicit_subject_this_by_name + }, + { + "component_explicit_subject_wildcard", + Parser_component_explicit_subject_wildcard + }, + { + "pair_implicit_subject", + Parser_pair_implicit_subject + }, + { + "pair_implicit_subject_wildcard_pred", + Parser_pair_implicit_subject_wildcard_pred + }, + { + "pair_implicit_subject_wildcard_obj", + Parser_pair_implicit_subject_wildcard_obj + }, + { + "pair_implicit_subject_this_pred", + Parser_pair_implicit_subject_this_pred + }, + { + "pair_implicit_subject_this_obj", + Parser_pair_implicit_subject_this_obj + }, + { + "pair_explicit_subject", + Parser_pair_explicit_subject + }, + { + "pair_explicit_subject_this", + Parser_pair_explicit_subject_this + }, + { + "pair_explicit_subject_this_by_name", + Parser_pair_explicit_subject_this_by_name + }, + { + "pair_explicit_subject_wildcard_pred", + Parser_pair_explicit_subject_wildcard_pred + }, + { + "pair_explicit_subject_wildcard_subj", + Parser_pair_explicit_subject_wildcard_subj + }, + { + "pair_explicit_subject_wildcard_obj", + Parser_pair_explicit_subject_wildcard_obj + }, + { + "in_component_implicit_subject", + Parser_in_component_implicit_subject + }, + { + "in_component_explicit_subject", + Parser_in_component_explicit_subject + }, + { + "in_pair_implicit_subject", + Parser_in_pair_implicit_subject + }, + { + "in_pair_explicit_subject", + Parser_in_pair_explicit_subject + }, + { + "inout_component_implicit_subject", + Parser_inout_component_implicit_subject + }, + { + "inout_component_explicit_subject", + Parser_inout_component_explicit_subject + }, + { + "inout_pair_implicit_subject", + Parser_inout_pair_implicit_subject + }, + { + "inout_pair_explicit_subject", + Parser_inout_pair_explicit_subject + }, + { + "out_component_implicit_subject", + Parser_out_component_implicit_subject + }, + { + "out_component_explicit_subject", + Parser_out_component_explicit_subject + }, + { + "out_pair_implicit_subject", + Parser_out_pair_implicit_subject + }, + { + "out_pair_explicit_subject", + Parser_out_pair_explicit_subject + }, + { + "component_singleton", + Parser_component_singleton + }, + { + "this_singleton", + Parser_this_singleton + }, + { + "component_implicit_no_subject", + Parser_component_implicit_no_subject + }, + { + "component_explicit_no_subject", + Parser_component_explicit_no_subject + }, + { + "pair_no_subject", + Parser_pair_no_subject + }, + { + "variable_single_char", + Parser_variable_single_char + }, + { + "variable_multi_char", + Parser_variable_multi_char + }, + { + "variable_multi_char_w_underscore", + Parser_variable_multi_char_w_underscore + }, + { + "variable_multi_char_w_number", + Parser_variable_multi_char_w_number + }, + { + "escaped_all_caps_single_char", + Parser_escaped_all_caps_single_char + }, + { + "escaped_all_caps_multi_char", + Parser_escaped_all_caps_multi_char + }, + { + "component_not", + Parser_component_not + }, + { + "pair_implicit_subject_not", + Parser_pair_implicit_subject_not + }, + { + "pair_explicit_subject_not", + Parser_pair_explicit_subject_not + }, + { + "2_component_not", + Parser_2_component_not + }, + { + "2_component_not_no_space", + Parser_2_component_not_no_space + }, + { + "component_optional", + Parser_component_optional + }, + { + "2_component_optional", + Parser_2_component_optional + }, + { + "2_component_optional_no_space", + Parser_2_component_optional_no_space + }, + { + "from_and", + Parser_from_and + }, + { + "from_or", + Parser_from_or + }, + { + "from_not", + Parser_from_not + }, + { + "pair_implicit_subject_optional", + Parser_pair_implicit_subject_optional + }, + { + "pair_explicit_subject_optional", + Parser_pair_explicit_subject_optional + }, + { + "pred_implicit_subject_w_role", + Parser_pred_implicit_subject_w_role + }, + { + "pred_explicit_subject_w_role", + Parser_pred_explicit_subject_w_role + }, + { + "pred_no_subject_w_role", + Parser_pred_no_subject_w_role + }, + { + "pair_implicit_subject_w_role", + Parser_pair_implicit_subject_w_role + }, + { + "pair_explicit_subject_w_role", + Parser_pair_explicit_subject_w_role + }, + { + "inout_role_pred_implicit_subject", + Parser_inout_role_pred_implicit_subject + }, + { + "inout_role_pred_no_subject", + Parser_inout_role_pred_no_subject + }, + { + "inout_role_pred_explicit_subject", + Parser_inout_role_pred_explicit_subject + }, + { + "inout_role_pair_implicit_subject", + Parser_inout_role_pair_implicit_subject + }, + { + "inout_role_pair_explicit_subject", + Parser_inout_role_pair_explicit_subject + }, + { + "2_pred_implicit_subject", + Parser_2_pred_implicit_subject + }, + { + "2_pred_no_subject", + Parser_2_pred_no_subject + }, + { + "2_pred_explicit_subject", + Parser_2_pred_explicit_subject + }, + { + "2_pair_implicit_subject", + Parser_2_pair_implicit_subject + }, + { + "2_pair_explicit_subject", + Parser_2_pair_explicit_subject + }, + { + "2_pred_role", + Parser_2_pred_role + }, + { + "2_pair_implicit_subj_role", + Parser_2_pair_implicit_subj_role + }, + { + "2_pair_explicit_subj_role", + Parser_2_pair_explicit_subj_role + }, + { + "2_or_pred_implicit_subj", + Parser_2_or_pred_implicit_subj + }, + { + "2_or_pred_explicit_subj", + Parser_2_or_pred_explicit_subj + }, + { + "2_or_pair_implicit_subj", + Parser_2_or_pair_implicit_subj + }, + { + "2_or_pair_explicit_subj", + Parser_2_or_pair_explicit_subj + }, + { + "2_or_pred_inout", + Parser_2_or_pred_inout + }, + { + "1_digit_pred_implicit_subj", + Parser_1_digit_pred_implicit_subj + }, + { + "1_digit_pred_no_subj", + Parser_1_digit_pred_no_subj + }, + { + "1_digit_pred_explicit_subj", + Parser_1_digit_pred_explicit_subj + }, + { + "1_digit_pair_implicit_subj", + Parser_1_digit_pair_implicit_subj + }, + { + "1_digit_pair_explicit_subj", + Parser_1_digit_pair_explicit_subj + }, + { + "pred_implicit_subject_self", + Parser_pred_implicit_subject_self + }, + { + "pred_implicit_subject_superset", + Parser_pred_implicit_subject_superset + }, + { + "pred_implicit_subject_subset", + Parser_pred_implicit_subject_subset + }, + { + "pred_implicit_subject_superset_inclusive", + Parser_pred_implicit_subject_superset_inclusive + }, + { + "pred_implicit_subject_subset_inclusive", + Parser_pred_implicit_subject_subset_inclusive + }, + { + "pred_implicit_subject_superset_cascade", + Parser_pred_implicit_subject_superset_cascade + }, + { + "pred_implicit_subject_subset_cascade", + Parser_pred_implicit_subject_subset_cascade + }, + { + "pred_implicit_subject_superset_inclusive_cascade", + Parser_pred_implicit_subject_superset_inclusive_cascade + }, + { + "pred_implicit_subject_subset_inclusive_cascade", + Parser_pred_implicit_subject_subset_inclusive_cascade + }, + { + "pred_implicit_subject_implicit_superset_cascade", + Parser_pred_implicit_subject_implicit_superset_cascade + }, + { + "pred_implicit_subject_implicit_superset_inclusive_cascade", + Parser_pred_implicit_subject_implicit_superset_inclusive_cascade + }, + { + "pred_implicit_subject_implicit_superset_cascade_w_rel", + Parser_pred_implicit_subject_implicit_superset_cascade_w_rel + }, + { + "pred_implicit_subject_implicit_superset_inclusive_cascade_w_rel", + Parser_pred_implicit_subject_implicit_superset_inclusive_cascade_w_rel + }, + { + "pred_implicit_subject_superset_depth_1_digit", + Parser_pred_implicit_subject_superset_depth_1_digit + }, + { + "pred_implicit_subject_subset_depth_1_digit", + Parser_pred_implicit_subject_subset_depth_1_digit + }, + { + "pred_implicit_subject_superset_depth_2_digits", + Parser_pred_implicit_subject_superset_depth_2_digits + }, + { + "pred_implicit_subject_subset_depth_2_digits", + Parser_pred_implicit_subject_subset_depth_2_digits + }, + { + "pred_implicit_superset_min_max_depth", + Parser_pred_implicit_superset_min_max_depth + }, + { + "pred_implicit_superset_childof_min_max_depth", + Parser_pred_implicit_superset_childof_min_max_depth + }, + { + "pred_implicit_subject_superset_childof", + Parser_pred_implicit_subject_superset_childof + }, + { + "pred_implicit_subject_cascade_superset_childof", + Parser_pred_implicit_subject_cascade_superset_childof + }, + { + "pred_implicit_subject_superset_cascade_childof", + Parser_pred_implicit_subject_superset_cascade_childof + }, + { + "pred_implicit_subject_superset_cascade_childof_optional", + Parser_pred_implicit_subject_superset_cascade_childof_optional + }, + { + "expr_w_symbol", + Parser_expr_w_symbol + } +}; + +bake_test_case Plecs_testcases[] = { + { + "null", + Plecs_null + }, + { + "empty", + Plecs_empty + }, + { + "space", + Plecs_space + }, + { + "space_newline", + Plecs_space_newline + }, + { + "empty_newline", + Plecs_empty_newline + }, + { + "entity", + Plecs_entity + }, + { + "entity_w_entity", + Plecs_entity_w_entity + }, + { + "entity_w_pair", + Plecs_entity_w_pair + }, + { + "2_entities", + Plecs_2_entities + }, + { + "2_entities_w_entities", + Plecs_2_entities_w_entities + }, + { + "3_entities_w_pairs", + Plecs_3_entities_w_pairs + } +}; + +bake_test_case GlobalComponentIds_testcases[] = { + { + "declare", + GlobalComponentIds_declare + }, + { + "declare_w_entity", + GlobalComponentIds_declare_w_entity + }, + { + "declare_2_world", + GlobalComponentIds_declare_2_world + }, + { + "declare_tag", + GlobalComponentIds_declare_tag + }, + { + "declare_tag_w_entity", + GlobalComponentIds_declare_tag_w_entity + }, + { + "declare_entity", + GlobalComponentIds_declare_entity + }, + { + "declare_type", + GlobalComponentIds_declare_type + } +}; + +bake_test_case Hierarchies_testcases[] = { + { + "empty_scope", + Hierarchies_empty_scope + }, + { + "get_parent", + Hierarchies_get_parent + }, + { + "get_parent_from_nested", + Hierarchies_get_parent_from_nested + }, + { + "get_parent_from_nested_2", + Hierarchies_get_parent_from_nested_2 + }, + { + "get_parent_from_root", + Hierarchies_get_parent_from_root + }, + { + "tree_iter_empty", + Hierarchies_tree_iter_empty + }, + { + "tree_iter_1_table", + Hierarchies_tree_iter_1_table + }, + { + "tree_iter_2_tables", + Hierarchies_tree_iter_2_tables + }, + { + "tree_iter_w_filter", + Hierarchies_tree_iter_w_filter + }, + { + "path_depth_0", + Hierarchies_path_depth_0 + }, + { + "path_depth_1", + Hierarchies_path_depth_1 + }, + { + "path_depth_2", + Hierarchies_path_depth_2 + }, + { + "rel_path_from_root", + Hierarchies_rel_path_from_root + }, + { + "rel_path_from_self", + Hierarchies_rel_path_from_self + }, + { + "rel_path_depth_1", + Hierarchies_rel_path_depth_1 + }, + { + "rel_path_depth_2", + Hierarchies_rel_path_depth_2 + }, + { + "rel_path_no_match", + Hierarchies_rel_path_no_match + }, + { + "path_custom_sep", + Hierarchies_path_custom_sep + }, + { + "path_custom_prefix", + Hierarchies_path_custom_prefix + }, + { + "path_prefix_rel_match", + Hierarchies_path_prefix_rel_match + }, + { + "path_prefix_rel_no_match", + Hierarchies_path_prefix_rel_no_match + }, + { + "fullpath_for_core", + Hierarchies_fullpath_for_core + }, + { + "path_w_number", + Hierarchies_path_w_number + }, + { + "lookup_depth_0", + Hierarchies_lookup_depth_0 + }, + { + "lookup_depth_1", + Hierarchies_lookup_depth_1 + }, + { + "lookup_depth_2", + Hierarchies_lookup_depth_2 + }, + { + "lookup_rel_0", + Hierarchies_lookup_rel_0 + }, + { + "lookup_rel_1", + Hierarchies_lookup_rel_1 + }, + { + "lookup_rel_2", + Hierarchies_lookup_rel_2 + }, + { + "lookup_custom_sep", + Hierarchies_lookup_custom_sep + }, + { + "lookup_custom_prefix", + Hierarchies_lookup_custom_prefix + }, + { + "lookup_custom_prefix_from_root", + Hierarchies_lookup_custom_prefix_from_root + }, + { + "lookup_self", + Hierarchies_lookup_self + }, + { + "lookup_in_parent_from_scope", + Hierarchies_lookup_in_parent_from_scope + }, + { + "lookup_in_root_from_scope", + Hierarchies_lookup_in_root_from_scope + }, + { + "lookup_number", + Hierarchies_lookup_number + }, + { + "delete_children", + Hierarchies_delete_children + }, + { + "scope_set", + Hierarchies_scope_set + }, + { + "scope_set_again", + Hierarchies_scope_set_again + }, + { + "scope_set_w_new", + Hierarchies_scope_set_w_new + }, + { + "scope_set_w_new_staged", + Hierarchies_scope_set_w_new_staged + }, + { + "scope_set_w_lookup", + Hierarchies_scope_set_w_lookup + }, + { + "scope_component", + Hierarchies_scope_component + }, + { + "scope_component_no_macro", + Hierarchies_scope_component_no_macro + }, + { + "new_from_path_depth_0", + Hierarchies_new_from_path_depth_0 + }, + { + "new_from_path_depth_1", + Hierarchies_new_from_path_depth_1 + }, + { + "new_from_path_depth_2", + Hierarchies_new_from_path_depth_2 + }, + { + "new_from_path_existing_depth_0", + Hierarchies_new_from_path_existing_depth_0 + }, + { + "new_from_path_existing_depth_1", + Hierarchies_new_from_path_existing_depth_1 + }, + { + "new_from_path_existing_depth_2", + Hierarchies_new_from_path_existing_depth_2 + }, + { + "add_path_depth_0", + Hierarchies_add_path_depth_0 + }, + { + "add_path_depth_1", + Hierarchies_add_path_depth_1 + }, + { + "add_path_depth_2", + Hierarchies_add_path_depth_2 + }, + { + "add_path_existing_depth_0", + Hierarchies_add_path_existing_depth_0 + }, + { + "add_path_existing_depth_1", + Hierarchies_add_path_existing_depth_1 + }, + { + "add_path_existing_depth_2", + Hierarchies_add_path_existing_depth_2 + }, + { + "add_path_from_scope", + Hierarchies_add_path_from_scope + }, + { + "add_path_from_scope_new_entity", + Hierarchies_add_path_from_scope_new_entity + }, + { + "new_w_child_in_root", + Hierarchies_new_w_child_in_root + }, + { + "delete_child", + Hierarchies_delete_child + }, + { + "delete_2_children", + Hierarchies_delete_2_children + }, + { + "delete_2_children_different_type", + Hierarchies_delete_2_children_different_type + }, + { + "delete_tree_2_levels", + Hierarchies_delete_tree_2_levels + }, + { + "delete_tree_3_levels", + Hierarchies_delete_tree_3_levels + }, + { + "delete_tree_count_tables", + Hierarchies_delete_tree_count_tables + }, + { + "delete_tree_staged", + Hierarchies_delete_tree_staged + }, + { + "delete_tree_empty_table", + Hierarchies_delete_tree_empty_table + }, + { + "delete_tree_recreate", + Hierarchies_delete_tree_recreate + }, + { + "delete_tree_w_onremove", + Hierarchies_delete_tree_w_onremove + }, + { + "delete_tree_w_dtor", + Hierarchies_delete_tree_w_dtor + }, + { + "get_child_count", + Hierarchies_get_child_count + }, + { + "get_child_count_2_tables", + Hierarchies_get_child_count_2_tables + }, + { + "get_child_count_no_children", + Hierarchies_get_child_count_no_children + }, + { + "scope_iter_after_delete_tree", + Hierarchies_scope_iter_after_delete_tree + }, + { + "add_child_after_delete_tree", + Hierarchies_add_child_after_delete_tree + }, + { + "add_child_to_recycled_parent", + Hierarchies_add_child_to_recycled_parent + }, + { + "get_type_after_recycled_parent_add", + Hierarchies_get_type_after_recycled_parent_add + }, + { + "rematch_after_add_to_recycled_parent", + Hierarchies_rematch_after_add_to_recycled_parent + }, + { + "cascade_after_recycled_parent_change", + Hierarchies_cascade_after_recycled_parent_change + }, + { + "long_name_depth_0", + Hierarchies_long_name_depth_0 + }, + { + "long_name_depth_1", + Hierarchies_long_name_depth_1 + }, + { + "long_name_depth_2", + Hierarchies_long_name_depth_2 + } +}; + +bake_test_case Add_bulk_testcases[] = { + { + "add_comp_from_comp_to_empty", + Add_bulk_add_comp_from_comp_to_empty + }, + { + "add_comp_from_comp_to_existing", + Add_bulk_add_comp_from_comp_to_existing + }, + { + "add_comp_from_tag_to_empty", + Add_bulk_add_comp_from_tag_to_empty + }, + { + "add_comp_from_tag_to_existing", + Add_bulk_add_comp_from_tag_to_existing + }, + { + "add_tag_from_tag_to_empty", + Add_bulk_add_tag_from_tag_to_empty + }, + { + "add_tag_from_tag_to_existing", + Add_bulk_add_tag_from_tag_to_existing + }, + { + "add_comp_to_more_existing", + Add_bulk_add_comp_to_more_existing + }, + { + "add_comp_to_fewer_existing", + Add_bulk_add_comp_to_fewer_existing + }, + { + "on_add", + Add_bulk_on_add + }, + { + "add_entity_comp", + Add_bulk_add_entity_comp + }, + { + "add_entity_tag", + Add_bulk_add_entity_tag + }, + { + "add_entity_on_add", + Add_bulk_add_entity_on_add + }, + { + "add_entity_existing", + Add_bulk_add_entity_existing + } +}; + +bake_test_case Remove_bulk_testcases[] = { + { + "remove_comp_from_comp_to_empty", + Remove_bulk_remove_comp_from_comp_to_empty + }, + { + "remove_comp_from_comp_to_existing", + Remove_bulk_remove_comp_from_comp_to_existing + }, + { + "remove_comp_from_tag_to_empty", + Remove_bulk_remove_comp_from_tag_to_empty + }, + { + "remove_comp_from_tag_to_existing", + Remove_bulk_remove_comp_from_tag_to_existing + }, + { + "remove_tag_from_tag_to_empty", + Remove_bulk_remove_tag_from_tag_to_empty + }, + { + "remove_tag_from_tag_to_existing", + Remove_bulk_remove_tag_from_tag_to_existing + }, + { + "remove_all_comp", + Remove_bulk_remove_all_comp + }, + { + "remove_all_tag", + Remove_bulk_remove_all_tag + }, + { + "on_remove", + Remove_bulk_on_remove + }, + { + "remove_entity_comp", + Remove_bulk_remove_entity_comp + }, + { + "remove_entity_tag", + Remove_bulk_remove_entity_tag + }, + { + "remove_entity_on_remove", + Remove_bulk_remove_entity_on_remove + }, + { + "bulk_remove_w_low_tag_id", + Remove_bulk_bulk_remove_w_low_tag_id + } +}; + +bake_test_case Add_remove_bulk_testcases[] = { + { + "add_remove_add_only", + Add_remove_bulk_add_remove_add_only + }, + { + "add_remove_remove_only", + Add_remove_bulk_add_remove_remove_only + }, + { + "add_remove_both", + Add_remove_bulk_add_remove_both + }, + { + "add_remove_same", + Add_remove_bulk_add_remove_same + } +}; + +bake_test_case Has_testcases[] = { + { + "zero", + Has_zero + }, + { + "zero_from_nonzero", + Has_zero_from_nonzero + }, + { + "1_of_0", + Has_1_of_0 + }, + { + "1_of_1", + Has_1_of_1 + }, + { + "1_of_2", + Has_1_of_2 + }, + { + "2_of_0", + Has_2_of_0 + }, + { + "2_of_2", + Has_2_of_2 + }, + { + "3_of_2", + Has_3_of_2 + }, + { + "2_of_1", + Has_2_of_1 + }, + { + "1_of_empty", + Has_1_of_empty + }, + { + "has_in_progress", + Has_has_in_progress + }, + { + "has_of_zero", + Has_has_of_zero + }, + { + "owns", + Has_owns + }, + { + "has_entity", + Has_has_entity + }, + { + "has_entity_0", + Has_has_entity_0 + }, + { + "has_entity_0_component", + Has_has_entity_0_component + }, + { + "has_entity_owned", + Has_has_entity_owned + }, + { + "has_entity_owned_0", + Has_has_entity_owned_0 + }, + { + "has_entity_owned_0_component", + Has_has_entity_owned_0_component + }, + { + "has_wildcard", + Has_has_wildcard + }, + { + "has_wildcard_pair", + Has_has_wildcard_pair + } +}; + +bake_test_case Count_testcases[] = { + { + "count_empty", + Count_count_empty + }, + { + "count_w_entity_0", + Count_count_w_entity_0 + }, + { + "count_1_component", + Count_count_1_component + }, + { + "count_2_components", + Count_count_2_components + }, + { + "count_3_components", + Count_count_3_components + }, + { + "count_2_types_2_comps", + Count_count_2_types_2_comps + } +}; + +bake_test_case Get_component_testcases[] = { + { + "get_empty", + Get_component_get_empty + }, + { + "get_1_from_1", + Get_component_get_1_from_1 + }, + { + "get_1_from_2", + Get_component_get_1_from_2 + }, + { + "get_2_from_2", + Get_component_get_2_from_2 + }, + { + "get_2_from_3", + Get_component_get_2_from_3 + }, + { + "get_1_from_2_in_progress_from_main_stage", + Get_component_get_1_from_2_in_progress_from_main_stage + }, + { + "get_1_from_2_add_in_progress", + Get_component_get_1_from_2_add_in_progress + }, + { + "get_both_from_2_add_in_progress", + Get_component_get_both_from_2_add_in_progress + }, + { + "get_both_from_2_add_remove_in_progress", + Get_component_get_both_from_2_add_remove_in_progress + }, + { + "get_childof_component", + Get_component_get_childof_component + } +}; + +bake_test_case Reference_testcases[] = { + { + "get_ref", + Reference_get_ref + }, + { + "get_ref_after_add", + Reference_get_ref_after_add + }, + { + "get_ref_after_remove", + Reference_get_ref_after_remove + }, + { + "get_ref_after_delete", + Reference_get_ref_after_delete + }, + { + "get_ref_after_realloc", + Reference_get_ref_after_realloc + }, + { + "get_ref_after_realloc_w_lifecycle", + Reference_get_ref_after_realloc_w_lifecycle + }, + { + "get_ref_staged", + Reference_get_ref_staged + }, + { + "get_ref_after_new_in_stage", + Reference_get_ref_after_new_in_stage + }, + { + "get_ref_monitored", + Reference_get_ref_monitored + }, + { + "get_nonexisting", + Reference_get_nonexisting + } +}; + +bake_test_case Delete_testcases[] = { + { + "delete_1", + Delete_delete_1 + }, + { + "delete_1_again", + Delete_delete_1_again + }, + { + "delete_empty", + Delete_delete_empty + }, + { + "delete_nonexist", + Delete_delete_nonexist + }, + { + "delete_1st_of_3", + Delete_delete_1st_of_3 + }, + { + "delete_2nd_of_3", + Delete_delete_2nd_of_3 + }, + { + "delete_2_of_3", + Delete_delete_2_of_3 + }, + { + "delete_3_of_3", + Delete_delete_3_of_3 + }, + { + "delete_w_on_remove", + Delete_delete_w_on_remove + }, + { + "clear_1_component", + Delete_clear_1_component + }, + { + "clear_2_components", + Delete_clear_2_components + }, + { + "alive_after_delete", + Delete_alive_after_delete + }, + { + "alive_after_clear", + Delete_alive_after_clear + }, + { + "alive_after_staged_delete", + Delete_alive_after_staged_delete + }, + { + "alive_while_staged", + Delete_alive_while_staged + }, + { + "alive_while_staged_w_delete", + Delete_alive_while_staged_w_delete + }, + { + "alive_while_staged_w_delete_recycled_id", + Delete_alive_while_staged_w_delete_recycled_id + }, + { + "alive_after_recycle", + Delete_alive_after_recycle + }, + { + "delete_recycled", + Delete_delete_recycled + }, + { + "get_alive_for_alive", + Delete_get_alive_for_alive + }, + { + "get_alive_for_recycled", + Delete_get_alive_for_recycled + }, + { + "get_alive_for_not_alive", + Delete_get_alive_for_not_alive + }, + { + "get_alive_w_generation_for_recycled_alive", + Delete_get_alive_w_generation_for_recycled_alive + }, + { + "get_alive_w_generation_for_recycled_not_alive", + Delete_get_alive_w_generation_for_recycled_not_alive + }, + { + "get_alive_for_0", + Delete_get_alive_for_0 + }, + { + "get_alive_for_nonexistent", + Delete_get_alive_for_nonexistent + }, + { + "move_w_dtor_move", + Delete_move_w_dtor_move + }, + { + "move_w_dtor_no_move", + Delete_move_w_dtor_no_move + }, + { + "move_w_no_dtor_move", + Delete_move_w_no_dtor_move + }, + { + "wrap_generation_count", + Delete_wrap_generation_count + } +}; + +bake_test_case OnDelete_testcases[] = { + { + "on_delete_id_default", + OnDelete_on_delete_id_default + }, + { + "on_delete_id_remove", + OnDelete_on_delete_id_remove + }, + { + "on_delete_id_delete", + OnDelete_on_delete_id_delete + }, + { + "on_delete_relation_default", + OnDelete_on_delete_relation_default + }, + { + "on_delete_relation_remove", + OnDelete_on_delete_relation_remove + }, + { + "on_delete_relation_delete", + OnDelete_on_delete_relation_delete + }, + { + "on_delete_object_default", + OnDelete_on_delete_object_default + }, + { + "on_delete_object_remove", + OnDelete_on_delete_object_remove + }, + { + "on_delete_object_delete", + OnDelete_on_delete_object_delete + }, + { + "on_delete_id_throw", + OnDelete_on_delete_id_throw + }, + { + "on_delete_relation_throw", + OnDelete_on_delete_relation_throw + }, + { + "on_delete_object_throw", + OnDelete_on_delete_object_throw + }, + { + "on_delete_id_remove_no_instances", + OnDelete_on_delete_id_remove_no_instances + }, + { + "on_delete_id_delete_no_instances", + OnDelete_on_delete_id_delete_no_instances + }, + { + "on_delete_id_throw_no_instances", + OnDelete_on_delete_id_throw_no_instances + }, + { + "on_delete_cyclic_id_default", + OnDelete_on_delete_cyclic_id_default + }, + { + "on_delete_cyclic_id_remove", + OnDelete_on_delete_cyclic_id_remove + }, + { + "on_delete_cyclic_id_remove_both", + OnDelete_on_delete_cyclic_id_remove_both + }, + { + "on_delete_cyclic_id_delete", + OnDelete_on_delete_cyclic_id_delete + }, + { + "on_delete_cyclic_id_delete_both", + OnDelete_on_delete_cyclic_id_delete_both + }, + { + "on_delete_cyclic_relation_default", + OnDelete_on_delete_cyclic_relation_default + }, + { + "on_delete_cyclic_relation_remove", + OnDelete_on_delete_cyclic_relation_remove + }, + { + "on_delete_cyclic_relation_remove_both", + OnDelete_on_delete_cyclic_relation_remove_both + }, + { + "on_delete_cyclic_relation_delete", + OnDelete_on_delete_cyclic_relation_delete + }, + { + "on_delete_cyclic_object_default", + OnDelete_on_delete_cyclic_object_default + }, + { + "on_delete_cyclic_object_remove", + OnDelete_on_delete_cyclic_object_remove + }, + { + "on_delete_cyclic_object_delete", + OnDelete_on_delete_cyclic_object_delete + }, + { + "on_delete_remove_2_comps", + OnDelete_on_delete_remove_2_comps + }, + { + "on_delete_remove_2_comps_to_existing_table", + OnDelete_on_delete_remove_2_comps_to_existing_table + }, + { + "on_delete_delete_recursive", + OnDelete_on_delete_delete_recursive + }, + { + "on_delete_component_throw", + OnDelete_on_delete_component_throw + }, + { + "on_delete_remove_2_relations", + OnDelete_on_delete_remove_2_relations + }, + { + "on_delete_remove_object_w_2_relations", + OnDelete_on_delete_remove_object_w_2_relations + }, + { + "on_delete_remove_object_w_5_relations", + OnDelete_on_delete_remove_object_w_5_relations + }, + { + "on_delete_remove_object_w_50_relations", + OnDelete_on_delete_remove_object_w_50_relations + }, + { + "on_delete_remove_object_w_50_relations_3_tables", + OnDelete_on_delete_remove_object_w_50_relations_3_tables + }, + { + "remove_id_from_2_tables", + OnDelete_remove_id_from_2_tables + }, + { + "remove_relation_from_2_tables", + OnDelete_remove_relation_from_2_tables + }, + { + "remove_object_from_2_tables", + OnDelete_remove_object_from_2_tables + }, + { + "remove_id_and_relation", + OnDelete_remove_id_and_relation + }, + { + "remove_id_and_relation_from_2_tables", + OnDelete_remove_id_and_relation_from_2_tables + }, + { + "stresstest_many_objects", + OnDelete_stresstest_many_objects + }, + { + "stresstest_many_relations", + OnDelete_stresstest_many_relations + }, + { + "stresstest_many_objects_on_delete", + OnDelete_stresstest_many_objects_on_delete + }, + { + "stresstest_many_relations_on_delete", + OnDelete_stresstest_many_relations_on_delete + }, + { + "on_delete_empty_table_w_on_remove", + OnDelete_on_delete_empty_table_w_on_remove + }, + { + "delete_table_in_on_remove_during_fini", + OnDelete_delete_table_in_on_remove_during_fini + }, + { + "delete_other_in_on_remove_during_fini", + OnDelete_delete_other_in_on_remove_during_fini + }, + { + "on_delete_remove_id_w_role", + OnDelete_on_delete_remove_id_w_role + }, + { + "on_delete_merge_pair_component", + OnDelete_on_delete_merge_pair_component + } +}; + +bake_test_case Delete_w_filter_testcases[] = { + { + "delete_1", + Delete_w_filter_delete_1 + }, + { + "delete_2", + Delete_w_filter_delete_2 + }, + { + "delete_1_2_types", + Delete_w_filter_delete_1_2_types + }, + { + "delete_2_2_types", + Delete_w_filter_delete_2_2_types + }, + { + "delete_except_1", + Delete_w_filter_delete_except_1 + }, + { + "delete_except_2", + Delete_w_filter_delete_except_2 + }, + { + "delete_with_any_of_2", + Delete_w_filter_delete_with_any_of_2 + }, + { + "delete_except_all_of_2", + Delete_w_filter_delete_except_all_of_2 + }, + { + "include_exact", + Delete_w_filter_include_exact + }, + { + "exclude_exact", + Delete_w_filter_exclude_exact + }, + { + "system_activate_test", + Delete_w_filter_system_activate_test + }, + { + "skip_builtin_tables", + Delete_w_filter_skip_builtin_tables + }, + { + "delete_w_on_remove", + Delete_w_filter_delete_w_on_remove + } +}; + +bake_test_case Set_testcases[] = { + { + "set_empty", + Set_set_empty + }, + { + "set_nonempty", + Set_set_nonempty + }, + { + "set_non_empty_override", + Set_set_non_empty_override + }, + { + "set_again", + Set_set_again + }, + { + "set_2", + Set_set_2 + }, + { + "add_set", + Set_add_set + }, + { + "set_add", + Set_set_add + }, + { + "set_add_other", + Set_set_add_other + }, + { + "set_remove", + Set_set_remove + }, + { + "set_remove_other", + Set_set_remove_other + }, + { + "set_remove_twice", + Set_set_remove_twice + }, + { + "set_and_new", + Set_set_and_new + }, + { + "set_null", + Set_set_null + }, + { + "get_mut_new", + Set_get_mut_new + }, + { + "get_mut_existing", + Set_get_mut_existing + }, + { + "get_mut_tag_new", + Set_get_mut_tag_new + }, + { + "get_mut_tag_existing", + Set_get_mut_tag_existing + }, + { + "get_mut_tag_new_w_comp", + Set_get_mut_tag_new_w_comp + }, + { + "get_mut_tag_existing_w_comp", + Set_get_mut_tag_existing_w_comp + }, + { + "get_mut_tag_new_w_pair", + Set_get_mut_tag_new_w_pair + }, + { + "get_mut_tag_existing_w_pair", + Set_get_mut_tag_existing_w_pair + }, + { + "modified_w_on_set", + Set_modified_w_on_set + }, + { + "modified_no_component", + Set_modified_no_component + }, + { + "get_mut_w_add_in_on_add", + Set_get_mut_w_add_in_on_add + }, + { + "get_mut_w_remove_in_on_add", + Set_get_mut_w_remove_in_on_add + }, + { + "emplace", + Set_emplace + }, + { + "emplace_existing", + Set_emplace_existing + }, + { + "emplace_w_move", + Set_emplace_w_move + } +}; + +bake_test_case Lookup_testcases[] = { + { + "lookup", + Lookup_lookup + }, + { + "lookup_component", + Lookup_lookup_component + }, + { + "lookup_not_found", + Lookup_lookup_not_found + }, + { + "lookup_child", + Lookup_lookup_child + }, + { + "lookup_w_null_name", + Lookup_lookup_w_null_name + }, + { + "get_name", + Lookup_get_name + }, + { + "get_name_no_name", + Lookup_get_name_no_name + }, + { + "get_name_from_empty", + Lookup_get_name_from_empty + }, + { + "lookup_by_id", + Lookup_lookup_by_id + }, + { + "lookup_symbol_by_id", + Lookup_lookup_symbol_by_id + }, + { + "lookup_name_w_digit", + Lookup_lookup_name_w_digit + }, + { + "lookup_symbol_w_digit", + Lookup_lookup_symbol_w_digit + }, + { + "lookup_path_w_digit", + Lookup_lookup_path_w_digit + }, + { + "set_name_of_existing", + Lookup_set_name_of_existing + }, + { + "change_name_of_existing", + Lookup_change_name_of_existing + }, + { + "lookup_alias", + Lookup_lookup_alias + }, + { + "lookup_scoped_alias", + Lookup_lookup_scoped_alias + }, + { + "define_duplicate_alias", + Lookup_define_duplicate_alias + }, + { + "lookup_null", + Lookup_lookup_null + }, + { + "lookup_symbol_null", + Lookup_lookup_symbol_null + }, + { + "lookup_this", + Lookup_lookup_this + }, + { + "lookup_wildcard", + Lookup_lookup_wildcard + }, + { + "lookup_path_this", + Lookup_lookup_path_this + }, + { + "lookup_path_wildcard", + Lookup_lookup_path_wildcard + }, + { + "lookup_path_this_from_scope", + Lookup_lookup_path_this_from_scope + }, + { + "lookup_path_wildcard_from_scope", + Lookup_lookup_path_wildcard_from_scope + } +}; + +bake_test_case Singleton_testcases[] = { + { + "set_get_singleton", + Singleton_set_get_singleton + }, + { + "get_mut_singleton", + Singleton_get_mut_singleton + }, + { + "singleton_system", + Singleton_singleton_system + } +}; + +bake_test_case Clone_testcases[] = { + { + "empty", + Clone_empty + }, + { + "empty_w_value", + Clone_empty_w_value + }, + { + "null", + Clone_null + }, + { + "null_w_value", + Clone_null_w_value + }, + { + "1_component", + Clone_1_component + }, + { + "2_component", + Clone_2_component + }, + { + "1_component_w_value", + Clone_1_component_w_value + }, + { + "2_component_w_value", + Clone_2_component_w_value + }, + { + "3_component", + Clone_3_component + }, + { + "3_component_w_value", + Clone_3_component_w_value + }, + { + "tag", + Clone_tag + }, + { + "tag_w_value", + Clone_tag_w_value + }, + { + "1_tag_1_component", + Clone_1_tag_1_component + }, + { + "1_tag_1_component_w_value", + Clone_1_tag_1_component_w_value + } +}; + +bake_test_case ComponentLifecycle_testcases[] = { + { + "ctor_on_add", + ComponentLifecycle_ctor_on_add + }, + { + "ctor_on_new", + ComponentLifecycle_ctor_on_new + }, + { + "dtor_on_remove", + ComponentLifecycle_dtor_on_remove + }, + { + "dtor_on_delete", + ComponentLifecycle_dtor_on_delete + }, + { + "copy_on_set", + ComponentLifecycle_copy_on_set + }, + { + "copy_on_override", + ComponentLifecycle_copy_on_override + }, + { + "copy_on_new_w_data", + ComponentLifecycle_copy_on_new_w_data + }, + { + "copy_on_clone", + ComponentLifecycle_copy_on_clone + }, + { + "no_copy_on_move", + ComponentLifecycle_no_copy_on_move + }, + { + "ctor_on_bulk_add", + ComponentLifecycle_ctor_on_bulk_add + }, + { + "dtor_on_bulk_remove", + ComponentLifecycle_dtor_on_bulk_remove + }, + { + "ctor_on_bulk_add_entity", + ComponentLifecycle_ctor_on_bulk_add_entity + }, + { + "dtor_on_bulk_remove_entity", + ComponentLifecycle_dtor_on_bulk_remove_entity + }, + { + "ctor_dtor_on_bulk_add_remove", + ComponentLifecycle_ctor_dtor_on_bulk_add_remove + }, + { + "ctor_copy_on_snapshot", + ComponentLifecycle_ctor_copy_on_snapshot + }, + { + "copy_on_snapshot", + ComponentLifecycle_copy_on_snapshot + }, + { + "dtor_on_restore", + ComponentLifecycle_dtor_on_restore + }, + { + "ctor_on_tag", + ComponentLifecycle_ctor_on_tag + }, + { + "dtor_on_tag", + ComponentLifecycle_dtor_on_tag + }, + { + "copy_on_tag", + ComponentLifecycle_copy_on_tag + }, + { + "move_on_tag", + ComponentLifecycle_move_on_tag + }, + { + "merge_to_different_table", + ComponentLifecycle_merge_to_different_table + }, + { + "merge_to_new_table", + ComponentLifecycle_merge_to_new_table + }, + { + "delete_in_stage", + ComponentLifecycle_delete_in_stage + }, + { + "ctor_on_add_pair", + ComponentLifecycle_ctor_on_add_pair + }, + { + "ctor_on_add_pair_set_ctor_after_table", + ComponentLifecycle_ctor_on_add_pair_set_ctor_after_table + }, + { + "ctor_on_add_pair_tag", + ComponentLifecycle_ctor_on_add_pair_tag + }, + { + "ctor_on_add_pair_tag_set_ctor_after_table", + ComponentLifecycle_ctor_on_add_pair_tag_set_ctor_after_table + }, + { + "ctor_on_move_pair", + ComponentLifecycle_ctor_on_move_pair + }, + { + "move_on_realloc", + ComponentLifecycle_move_on_realloc + }, + { + "move_on_dim", + ComponentLifecycle_move_on_dim + }, + { + "move_on_bulk_new", + ComponentLifecycle_move_on_bulk_new + }, + { + "move_on_delete", + ComponentLifecycle_move_on_delete + }, + { + "move_dtor_on_delete", + ComponentLifecycle_move_dtor_on_delete + }, + { + "copy_on_override_pair", + ComponentLifecycle_copy_on_override_pair + }, + { + "copy_on_override_pair_tag", + ComponentLifecycle_copy_on_override_pair_tag + }, + { + "copy_on_set_pair", + ComponentLifecycle_copy_on_set_pair + }, + { + "copy_on_set_pair_tag", + ComponentLifecycle_copy_on_set_pair_tag + }, + { + "prevent_lifecycle_overwrite", + ComponentLifecycle_prevent_lifecycle_overwrite + }, + { + "prevent_lifecycle_overwrite_null_callbacks", + ComponentLifecycle_prevent_lifecycle_overwrite_null_callbacks + }, + { + "allow_lifecycle_overwrite_equal_callbacks", + ComponentLifecycle_allow_lifecycle_overwrite_equal_callbacks + }, + { + "set_lifecycle_after_trigger", + ComponentLifecycle_set_lifecycle_after_trigger + }, + { + "valid_entity_in_dtor_after_delete", + ComponentLifecycle_valid_entity_in_dtor_after_delete + }, + { + "ctor_w_emplace", + ComponentLifecycle_ctor_w_emplace + }, + { + "dtor_on_fini", + ComponentLifecycle_dtor_on_fini + }, + { + "valid_type_in_dtor_on_fini", + ComponentLifecycle_valid_type_in_dtor_on_fini + }, + { + "valid_other_type_of_entity_in_dtor_on_fini", + ComponentLifecycle_valid_other_type_of_entity_in_dtor_on_fini + }, + { + "valid_same_type_comp_of_entity_in_dtor_on_fini", + ComponentLifecycle_valid_same_type_comp_of_entity_in_dtor_on_fini + }, + { + "valid_same_type_comp_of_entity_in_dtor_on_delete_parent", + ComponentLifecycle_valid_same_type_comp_of_entity_in_dtor_on_delete_parent + }, + { + "valid_entity_bulk_remove_all_components", + ComponentLifecycle_valid_entity_bulk_remove_all_components + }, + { + "delete_in_dtor_same_type_on_fini", + ComponentLifecycle_delete_in_dtor_same_type_on_fini + }, + { + "delete_in_dtor_other_type_on_fini", + ComponentLifecycle_delete_in_dtor_other_type_on_fini + }, + { + "delete_self_in_dtor_on_fini", + ComponentLifecycle_delete_self_in_dtor_on_fini + }, + { + "delete_in_dtor_same_type_on_delete_parent", + ComponentLifecycle_delete_in_dtor_same_type_on_delete_parent + }, + { + "delete_in_dtor_other_type_on_delete_parent", + ComponentLifecycle_delete_in_dtor_other_type_on_delete_parent + }, + { + "delete_self_in_dtor_on_delete_parent", + ComponentLifecycle_delete_self_in_dtor_on_delete_parent + }, + { + "delete_in_dtor_same_type_on_delete", + ComponentLifecycle_delete_in_dtor_same_type_on_delete + }, + { + "delete_in_dtor_other_type_on_delete", + ComponentLifecycle_delete_in_dtor_other_type_on_delete + }, + { + "delete_self_in_dtor_on_delete", + ComponentLifecycle_delete_self_in_dtor_on_delete + }, + { + "on_set_after_set", + ComponentLifecycle_on_set_after_set + }, + { + "on_set_after_new_w_data", + ComponentLifecycle_on_set_after_new_w_data + } +}; + +bake_test_case Pipeline_testcases[] = { + { + "system_order_same_phase", + Pipeline_system_order_same_phase + }, + { + "system_order_same_phase_after_disable", + Pipeline_system_order_same_phase_after_disable + }, + { + "system_order_same_phase_after_activate", + Pipeline_system_order_same_phase_after_activate + }, + { + "system_order_different_phase", + Pipeline_system_order_different_phase + }, + { + "system_order_different_phase_after_disable", + Pipeline_system_order_different_phase_after_disable + }, + { + "system_order_different_phase_after_activate", + Pipeline_system_order_different_phase_after_activate + }, + { + "system_order_after_new_system_lower_id", + Pipeline_system_order_after_new_system_lower_id + }, + { + "system_order_after_new_system_inbetween_id", + Pipeline_system_order_after_new_system_inbetween_id + }, + { + "system_order_after_new_system_higher_id", + Pipeline_system_order_after_new_system_higher_id + }, + { + "merge_after_staged_out", + Pipeline_merge_after_staged_out + }, + { + "merge_after_not_out", + Pipeline_merge_after_not_out + }, + { + "no_merge_after_main_out", + Pipeline_no_merge_after_main_out + }, + { + "no_merge_after_staged_in_out", + Pipeline_no_merge_after_staged_in_out + }, + { + "merge_after_staged_out_before_owned", + Pipeline_merge_after_staged_out_before_owned + }, + { + "switch_pipeline", + Pipeline_switch_pipeline + }, + { + "run_pipeline", + Pipeline_run_pipeline + }, + { + "get_pipeline_from_stage", + Pipeline_get_pipeline_from_stage + }, + { + "3_systems_3_types", + Pipeline_3_systems_3_types + }, + { + "random_read_after_random_write_out_in", + Pipeline_random_read_after_random_write_out_in + }, + { + "random_read_after_random_write_inout_in", + Pipeline_random_read_after_random_write_inout_in + }, + { + "random_read_after_random_write_out_inout", + Pipeline_random_read_after_random_write_out_inout + }, + { + "random_read_after_random_write_inout_inout", + Pipeline_random_read_after_random_write_inout_inout + }, + { + "random_read_after_random_write_w_not_write", + Pipeline_random_read_after_random_write_w_not_write + }, + { + "random_read_after_random_write_w_not_read", + Pipeline_random_read_after_random_write_w_not_read + }, + { + "random_read_after_random_write_w_wildcard", + Pipeline_random_read_after_random_write_w_wildcard + }, + { + "random_in_after_random_inout_after_random_out", + Pipeline_random_in_after_random_inout_after_random_out + } +}; + +bake_test_case SystemMisc_testcases[] = { + { + "invalid_not_without_id", + SystemMisc_invalid_not_without_id + }, + { + "invalid_optional_without_id", + SystemMisc_invalid_optional_without_id + }, + { + "invalid_system_without_id", + SystemMisc_invalid_system_without_id + }, + { + "invalid_container_without_id", + SystemMisc_invalid_container_without_id + }, + { + "invalid_cascade_without_id", + SystemMisc_invalid_cascade_without_id + }, + { + "invalid_entity_without_id", + SystemMisc_invalid_entity_without_id + }, + { + "invalid_empty_without_id", + SystemMisc_invalid_empty_without_id + }, + { + "invalid_empty_element", + SystemMisc_invalid_empty_element + }, + { + "invalid_empty_element_w_space", + SystemMisc_invalid_empty_element_w_space + }, + { + "invalid_empty_or", + SystemMisc_invalid_empty_or + }, + { + "invalid_empty_or_w_space", + SystemMisc_invalid_empty_or_w_space + }, + { + "invalid_or_w_not", + SystemMisc_invalid_or_w_not + }, + { + "invalid_not_w_or", + SystemMisc_invalid_not_w_or + }, + { + "invalid_0_w_and", + SystemMisc_invalid_0_w_and + }, + { + "invalid_0_w_from_system", + SystemMisc_invalid_0_w_from_system + }, + { + "invalid_0_w_from_container", + SystemMisc_invalid_0_w_from_container + }, + { + "invalid_0_w_from_cascade", + SystemMisc_invalid_0_w_from_cascade + }, + { + "invalid_0_w_from_entity", + SystemMisc_invalid_0_w_from_entity + }, + { + "invalid_0_w_from_empty", + SystemMisc_invalid_0_w_from_empty + }, + { + "invalid_or_w_empty", + SystemMisc_invalid_or_w_empty + }, + { + "invalid_component_id", + SystemMisc_invalid_component_id + }, + { + "invalid_entity_id", + SystemMisc_invalid_entity_id + }, + { + "invalid_null_string", + SystemMisc_invalid_null_string + }, + { + "invalid_empty_string", + SystemMisc_invalid_empty_string + }, + { + "invalid_empty_string_w_space", + SystemMisc_invalid_empty_string_w_space + }, + { + "invalid_mixed_src_modifier", + SystemMisc_invalid_mixed_src_modifier + }, + { + "redefine_row_system", + SystemMisc_redefine_row_system + }, + { + "system_w_or_prefab", + SystemMisc_system_w_or_prefab + }, + { + "system_w_or_disabled", + SystemMisc_system_w_or_disabled + }, + { + "system_w_or_disabled_and_prefab", + SystemMisc_system_w_or_disabled_and_prefab + }, + { + "table_columns_access", + SystemMisc_table_columns_access + }, + { + "status_enable_after_new", + SystemMisc_status_enable_after_new + }, + { + "status_enable_after_disable", + SystemMisc_status_enable_after_disable + }, + { + "status_disable_after_new", + SystemMisc_status_disable_after_new + }, + { + "status_disable_after_disable", + SystemMisc_status_disable_after_disable + }, + { + "status_activate_after_new", + SystemMisc_status_activate_after_new + }, + { + "status_deactivate_after_delete", + SystemMisc_status_deactivate_after_delete + }, + { + "dont_enable_after_rematch", + SystemMisc_dont_enable_after_rematch + }, + { + "ensure_single_merge", + SystemMisc_ensure_single_merge + }, + { + "table_count", + SystemMisc_table_count + }, + { + "match_system", + SystemMisc_match_system + }, + { + "match_system_w_filter", + SystemMisc_match_system_w_filter + }, + { + "system_initial_state", + SystemMisc_system_initial_state + }, + { + "add_own_component", + SystemMisc_add_own_component + }, + { + "change_system_action", + SystemMisc_change_system_action + }, + { + "system_readeactivate", + SystemMisc_system_readeactivate + }, + { + "system_readeactivate_w_2_systems", + SystemMisc_system_readeactivate_w_2_systems + }, + { + "add_to_system_in_progress", + SystemMisc_add_to_system_in_progress + }, + { + "add_to_lazy_system_in_progress", + SystemMisc_add_to_lazy_system_in_progress + }, + { + "redefine_null_signature", + SystemMisc_redefine_null_signature + }, + { + "redefine_0_signature", + SystemMisc_redefine_0_signature + }, + { + "one_named_column_of_two", + SystemMisc_one_named_column_of_two + }, + { + "two_named_columns_of_two", + SystemMisc_two_named_columns_of_two + }, + { + "get_column_by_name", + SystemMisc_get_column_by_name + }, + { + "get_column_by_name_not_found", + SystemMisc_get_column_by_name_not_found + }, + { + "get_column_by_name_no_names", + SystemMisc_get_column_by_name_no_names + }, + { + "redeclare_system_same_expr", + SystemMisc_redeclare_system_same_expr + }, + { + "redeclare_system_null_expr", + SystemMisc_redeclare_system_null_expr + }, + { + "redeclare_system_0_expr", + SystemMisc_redeclare_system_0_expr + }, + { + "redeclare_system_different_expr", + SystemMisc_redeclare_system_different_expr + }, + { + "redeclare_system_null_and_expr", + SystemMisc_redeclare_system_null_and_expr + }, + { + "redeclare_system_expr_and_null", + SystemMisc_redeclare_system_expr_and_null + }, + { + "redeclare_system_expr_and_0", + SystemMisc_redeclare_system_expr_and_0 + }, + { + "redeclare_system_0_and_expr", + SystemMisc_redeclare_system_0_and_expr + }, + { + "redeclare_system_0_and_null", + SystemMisc_redeclare_system_0_and_null + }, + { + "redeclare_system_null_and_0", + SystemMisc_redeclare_system_null_and_0 + }, + { + "redeclare_system_explicit_id", + SystemMisc_redeclare_system_explicit_id + }, + { + "redeclare_system_explicit_id_null_expr", + SystemMisc_redeclare_system_explicit_id_null_expr + }, + { + "redeclare_system_explicit_id_no_name", + SystemMisc_redeclare_system_explicit_id_no_name + }, + { + "declare_different_id_same_name", + SystemMisc_declare_different_id_same_name + }, + { + "declare_different_id_same_name_w_scope", + SystemMisc_declare_different_id_same_name_w_scope + }, + { + "rw_in_implicit_any", + SystemMisc_rw_in_implicit_any + }, + { + "rw_in_implicit_shared", + SystemMisc_rw_in_implicit_shared + }, + { + "rw_in_implicit_from_empty", + SystemMisc_rw_in_implicit_from_empty + }, + { + "rw_in_implicit_from_entity", + SystemMisc_rw_in_implicit_from_entity + }, + { + "rw_out_explicit_any", + SystemMisc_rw_out_explicit_any + }, + { + "rw_out_explicit_shared", + SystemMisc_rw_out_explicit_shared + }, + { + "rw_out_explicit_from_empty", + SystemMisc_rw_out_explicit_from_empty + }, + { + "rw_out_explicit_from_entity", + SystemMisc_rw_out_explicit_from_entity + }, + { + "activate_system_for_table_w_n_pairs", + SystemMisc_activate_system_for_table_w_n_pairs + }, + { + "get_query", + SystemMisc_get_query + }, + { + "set_get_context", + SystemMisc_set_get_context + }, + { + "set_get_binding_context", + SystemMisc_set_get_binding_context + }, + { + "deactivate_after_disable", + SystemMisc_deactivate_after_disable + }, + { + "system_w_self", + SystemMisc_system_w_self + }, + { + "delete_system", + SystemMisc_delete_system + }, + { + "delete_pipeline_system", + SystemMisc_delete_pipeline_system + }, + { + "delete_system_w_ctx", + SystemMisc_delete_system_w_ctx + } +}; + +bake_test_case Sorting_testcases[] = { + { + "sort_by_component", + Sorting_sort_by_component + }, + { + "sort_by_component_2_tables", + Sorting_sort_by_component_2_tables + }, + { + "sort_by_component_3_tables", + Sorting_sort_by_component_3_tables + }, + { + "sort_by_entity", + Sorting_sort_by_entity + }, + { + "sort_after_add", + Sorting_sort_after_add + }, + { + "sort_after_remove", + Sorting_sort_after_remove + }, + { + "sort_after_delete", + Sorting_sort_after_delete + }, + { + "sort_after_set", + Sorting_sort_after_set + }, + { + "sort_after_system", + Sorting_sort_after_system + }, + { + "sort_after_query", + Sorting_sort_after_query + }, + { + "sort_by_component_same_value_1", + Sorting_sort_by_component_same_value_1 + }, + { + "sort_by_component_same_value_2", + Sorting_sort_by_component_same_value_2 + }, + { + "sort_by_component_move_pivot", + Sorting_sort_by_component_move_pivot + }, + { + "sort_1000_entities", + Sorting_sort_1000_entities + }, + { + "sort_1000_entities_w_duplicates", + Sorting_sort_1000_entities_w_duplicates + }, + { + "sort_1000_entities_again", + Sorting_sort_1000_entities_again + }, + { + "sort_1000_entities_2_types", + Sorting_sort_1000_entities_2_types + }, + { + "sort_1500_entities_3_types", + Sorting_sort_1500_entities_3_types + }, + { + "sort_2000_entities_4_types", + Sorting_sort_2000_entities_4_types + }, + { + "sort_2_entities_2_types", + Sorting_sort_2_entities_2_types + }, + { + "sort_3_entities_3_types", + Sorting_sort_3_entities_3_types + }, + { + "sort_3_entities_3_types_2", + Sorting_sort_3_entities_3_types_2 + }, + { + "sort_4_entities_4_types", + Sorting_sort_4_entities_4_types + }, + { + "sort_1000_entities_2_types_again", + Sorting_sort_1000_entities_2_types_again + }, + { + "sort_1000_entities_add_type_after_sort", + Sorting_sort_1000_entities_add_type_after_sort + }, + { + "sort_shared_component", + Sorting_sort_shared_component + }, + { + "sort_w_tags_only", + Sorting_sort_w_tags_only + }, + { + "sort_childof_marked", + Sorting_sort_childof_marked + }, + { + "sort_isa_marked", + Sorting_sort_isa_marked + }, + { + "sort_relation_marked", + Sorting_sort_relation_marked + } +}; + +bake_test_case Filter_testcases[] = { + { + "filter_1_term", + Filter_filter_1_term + }, + { + "filter_2_terms", + Filter_filter_2_terms + }, + { + "filter_3_terms", + Filter_filter_3_terms + }, + { + "filter_3_terms_w_or", + Filter_filter_3_terms_w_or + }, + { + "filter_4_terms_w_or_at_1", + Filter_filter_4_terms_w_or_at_1 + }, + { + "filter_w_pair_id", + Filter_filter_w_pair_id + }, + { + "filter_w_pred_obj", + Filter_filter_w_pred_obj + }, + { + "filter_move", + Filter_filter_move + }, + { + "filter_copy", + Filter_filter_copy + }, + { + "filter_w_resources_copy", + Filter_filter_w_resources_copy + }, + { + "filter_w_10_terms", + Filter_filter_w_10_terms + }, + { + "filter_w_10_terms_move", + Filter_filter_w_10_terms_move + }, + { + "filter_w_10_terms_copy", + Filter_filter_w_10_terms_copy + }, + { + "term_w_id", + Filter_term_w_id + }, + { + "term_w_pair_id", + Filter_term_w_pair_id + }, + { + "term_w_pred_obj", + Filter_term_w_pred_obj + }, + { + "term_w_pair_finalize_twice", + Filter_term_w_pair_finalize_twice + }, + { + "term_w_role", + Filter_term_w_role + }, + { + "term_w_pred_role", + Filter_term_w_pred_role + }, + { + "term_iter_component", + Filter_term_iter_component + }, + { + "term_iter_tag", + Filter_term_iter_tag + }, + { + "term_iter_pair", + Filter_term_iter_pair + }, + { + "term_iter_pair_w_rel_wildcard", + Filter_term_iter_pair_w_rel_wildcard + }, + { + "term_iter_pair_w_obj_wildcard", + Filter_term_iter_pair_w_obj_wildcard + }, + { + "term_iter_w_superset", + Filter_term_iter_w_superset + }, + { + "term_iter_w_superset_childof", + Filter_term_iter_w_superset_childof + }, + { + "term_iter_w_superset_self", + Filter_term_iter_w_superset_self + }, + { + "term_iter_w_superset_self_childof", + Filter_term_iter_w_superset_self_childof + }, + { + "term_iter_w_superset_tag", + Filter_term_iter_w_superset_tag + }, + { + "term_iter_w_superset_pair", + Filter_term_iter_w_superset_pair + }, + { + "term_iter_w_superset_pair_obj_wildcard", + Filter_term_iter_w_superset_pair_obj_wildcard + }, + { + "term_iter_in_stage", + Filter_term_iter_in_stage + }, + { + "filter_iter_1_tag", + Filter_filter_iter_1_tag + }, + { + "filter_iter_2_tags", + Filter_filter_iter_2_tags + }, + { + "filter_iter_2_tags_1_not", + Filter_filter_iter_2_tags_1_not + }, + { + "filter_iter_3_tags_2_or", + Filter_filter_iter_3_tags_2_or + }, + { + "filter_iter_1_component", + Filter_filter_iter_1_component + }, + { + "filter_iter_2_components", + Filter_filter_iter_2_components + }, + { + "filter_iter_pair_id", + Filter_filter_iter_pair_id + }, + { + "filter_iter_2_pair_ids", + Filter_filter_iter_2_pair_ids + }, + { + "filter_iter_pair_pred_obj", + Filter_filter_iter_pair_pred_obj + }, + { + "filter_iter_pair_2_pred_obj", + Filter_filter_iter_pair_2_pred_obj + }, + { + "filter_iter_null", + Filter_filter_iter_null + }, + { + "filter_iter_1_not_tag", + Filter_filter_iter_1_not_tag + }, + { + "filter_iter_2_tags_1_optional", + Filter_filter_iter_2_tags_1_optional + }, + { + "filter_iter_in_stage", + Filter_filter_iter_in_stage + }, + { + "filter_iter_10_tags", + Filter_filter_iter_10_tags + }, + { + "filter_iter_20_tags", + Filter_filter_iter_20_tags + }, + { + "filter_iter_10_components", + Filter_filter_iter_10_components + }, + { + "filter_iter_20_components", + Filter_filter_iter_20_components + } +}; + +bake_test_case Query_testcases[] = { + { + "query_changed_after_new", + Query_query_changed_after_new + }, + { + "query_changed_after_delete", + Query_query_changed_after_delete + }, + { + "query_changed_after_add", + Query_query_changed_after_add + }, + { + "query_changed_after_remove", + Query_query_changed_after_remove + }, + { + "query_changed_after_set", + Query_query_changed_after_set + }, + { + "query_change_after_modified", + Query_query_change_after_modified + }, + { + "query_change_after_out_system", + Query_query_change_after_out_system + }, + { + "query_change_after_in_system", + Query_query_change_after_in_system + }, + { + "subquery_match_existing", + Query_subquery_match_existing + }, + { + "subquery_match_new", + Query_subquery_match_new + }, + { + "subquery_inactive", + Query_subquery_inactive + }, + { + "subquery_unmatch", + Query_subquery_unmatch + }, + { + "subquery_rematch", + Query_subquery_rematch + }, + { + "subquery_rematch_w_parent_optional", + Query_subquery_rematch_w_parent_optional + }, + { + "subquery_rematch_w_sub_optional", + Query_subquery_rematch_w_sub_optional + }, + { + "query_single_pairs", + Query_query_single_pairs + }, + { + "query_single_instanceof", + Query_query_single_instanceof + }, + { + "query_single_childof", + Query_query_single_childof + }, + { + "query_w_filter", + Query_query_w_filter + }, + { + "query_optional_owned", + Query_query_optional_owned + }, + { + "query_optional_shared", + Query_query_optional_shared + }, + { + "query_optional_shared_nested", + Query_query_optional_shared_nested + }, + { + "query_optional_any", + Query_query_optional_any + }, + { + "query_rematch_optional_after_add", + Query_query_rematch_optional_after_add + }, + { + "get_owned_tag", + Query_get_owned_tag + }, + { + "get_shared_tag", + Query_get_shared_tag + }, + { + "explicit_delete", + Query_explicit_delete + }, + { + "get_column_size", + Query_get_column_size + }, + { + "orphaned_query", + Query_orphaned_query + }, + { + "nested_orphaned_query", + Query_nested_orphaned_query + }, + { + "invalid_access_orphaned_query", + Query_invalid_access_orphaned_query + }, + { + "stresstest_query_free", + Query_stresstest_query_free + }, + { + "only_from_entity", + Query_only_from_entity + }, + { + "only_from_singleton", + Query_only_from_singleton + }, + { + "only_not_from_entity", + Query_only_not_from_entity + }, + { + "only_not_from_singleton", + Query_only_not_from_singleton + }, + { + "get_filter", + Query_get_filter + }, + { + "group_by", + Query_group_by + }, + { + "group_by_w_ctx", + Query_group_by_w_ctx + }, + { + "iter_valid", + Query_iter_valid + }, + { + "query_optional_tag", + Query_query_optional_tag + }, + { + "query_optional_shared_tag", + Query_query_optional_shared_tag + }, + { + "query_iter_10_tags", + Query_query_iter_10_tags + }, + { + "query_iter_20_tags", + Query_query_iter_20_tags + }, + { + "query_iter_10_components", + Query_query_iter_10_components + }, + { + "query_iter_20_components", + Query_query_iter_20_components + } +}; + +bake_test_case Pairs_testcases[] = { + { + "type_w_one_pair", + Pairs_type_w_one_pair + }, + { + "type_w_two_pairs", + Pairs_type_w_two_pairs + }, + { + "add_pair", + Pairs_add_pair + }, + { + "remove_pair", + Pairs_remove_pair + }, + { + "add_tag_pair_for_tag", + Pairs_add_tag_pair_for_tag + }, + { + "add_tag_pair_for_component", + Pairs_add_tag_pair_for_component + }, + { + "query_2_pairs", + Pairs_query_2_pairs + }, + { + "query_2_pairs_2_instances_per_type", + Pairs_query_2_pairs_2_instances_per_type + }, + { + "query_pair_or_component", + Pairs_query_pair_or_component + }, + { + "query_pair_or_pair", + Pairs_query_pair_or_pair + }, + { + "query_not_pair", + Pairs_query_not_pair + }, + { + "override_pair", + Pairs_override_pair + }, + { + "override_tag_pair", + Pairs_override_tag_pair + }, + { + "pair_w_component_query", + Pairs_pair_w_component_query + }, + { + "on_add_pair", + Pairs_on_add_pair + }, + { + "on_add_pair_tag", + Pairs_on_add_pair_tag + }, + { + "on_remove_pair", + Pairs_on_remove_pair + }, + { + "on_remove_pair_tag", + Pairs_on_remove_pair_tag + }, + { + "on_remove_pair_on_delete", + Pairs_on_remove_pair_on_delete + }, + { + "on_remove_pair_tag_on_delete", + Pairs_on_remove_pair_tag_on_delete + }, + { + "get_typeid_w_recycled_rel", + Pairs_get_typeid_w_recycled_rel + }, + { + "get_typeid_w_recycled_obj", + Pairs_get_typeid_w_recycled_obj + }, + { + "id_str_w_recycled_rel", + Pairs_id_str_w_recycled_rel + }, + { + "id_str_w_recycled_obj", + Pairs_id_str_w_recycled_obj + }, + { + "set_object_w_zero_sized_rel_comp", + Pairs_set_object_w_zero_sized_rel_comp + }, + { + "dsl_pair", + Pairs_dsl_pair + }, + { + "dsl_pair_w_pred_wildcard", + Pairs_dsl_pair_w_pred_wildcard + }, + { + "dsl_pair_w_obj_wildcard", + Pairs_dsl_pair_w_obj_wildcard + }, + { + "dsl_pair_w_both_wildcard", + Pairs_dsl_pair_w_both_wildcard + }, + { + "dsl_pair_w_explicit_subj_this", + Pairs_dsl_pair_w_explicit_subj_this + }, + { + "dsl_pair_w_explicit_subj", + Pairs_dsl_pair_w_explicit_subj + }, + { + "api_pair", + Pairs_api_pair + }, + { + "api_pair_w_pred_wildcard", + Pairs_api_pair_w_pred_wildcard + }, + { + "api_pair_w_obj_wildcard", + Pairs_api_pair_w_obj_wildcard + }, + { + "api_pair_w_both_wildcard", + Pairs_api_pair_w_both_wildcard + }, + { + "api_pair_w_explicit_subj_this", + Pairs_api_pair_w_explicit_subj_this + }, + { + "api_pair_w_explicit_subj", + Pairs_api_pair_w_explicit_subj + }, + { + "typeid_from_tag", + Pairs_typeid_from_tag + }, + { + "typeid_from_component", + Pairs_typeid_from_component + }, + { + "typeid_from_pair", + Pairs_typeid_from_pair + }, + { + "typeid_from_pair_w_rel_type", + Pairs_typeid_from_pair_w_rel_type + }, + { + "typeid_from_pair_w_obj_type", + Pairs_typeid_from_pair_w_obj_type + }, + { + "typeid_from_pair_w_rel_obj_type", + Pairs_typeid_from_pair_w_rel_obj_type + }, + { + "typeid_from_pair_w_rel_0_obj_type", + Pairs_typeid_from_pair_w_rel_0_obj_type + }, + { + "typeid_from_pair_w_rel_obj_0_type", + Pairs_typeid_from_pair_w_rel_obj_0_type + }, + { + "typeid_from_pair_w_rel_0_obj_0_type", + Pairs_typeid_from_pair_w_rel_0_obj_0_type + }, + { + "tag_pair_w_rel_comp", + Pairs_tag_pair_w_rel_comp + }, + { + "tag_pair_w_obj_comp", + Pairs_tag_pair_w_obj_comp + }, + { + "tag_pair_w_rel_obj_comp", + Pairs_tag_pair_w_rel_obj_comp + }, + { + "get_tag_pair_w_rel_comp", + Pairs_get_tag_pair_w_rel_comp + }, + { + "get_tag_pair_w_obj_comp", + Pairs_get_tag_pair_w_obj_comp + }, + { + "get_tag_pair_w_rel_obj_comp", + Pairs_get_tag_pair_w_rel_obj_comp + }, + { + "tag_pair_w_childof_w_comp", + Pairs_tag_pair_w_childof_w_comp + }, + { + "tag_pair_w_isa_w_comp", + Pairs_tag_pair_w_isa_w_comp + }, + { + "get_1_object", + Pairs_get_1_object + }, + { + "get_1_object_not_found", + Pairs_get_1_object_not_found + }, + { + "get_n_objects", + Pairs_get_n_objects + } +}; + +bake_test_case Trigger_testcases[] = { + { + "on_add_trigger_before_table", + Trigger_on_add_trigger_before_table + }, + { + "on_add_trigger_after_table", + Trigger_on_add_trigger_after_table + }, + { + "on_remove_trigger_before_table", + Trigger_on_remove_trigger_before_table + }, + { + "on_remove_trigger_after_table", + Trigger_on_remove_trigger_after_table + }, + { + "on_add_tag", + Trigger_on_add_tag + }, + { + "on_add_component", + Trigger_on_add_component + }, + { + "on_add_wildcard", + Trigger_on_add_wildcard + }, + { + "on_add_pair", + Trigger_on_add_pair + }, + { + "on_add_pair_obj_wildcard", + Trigger_on_add_pair_obj_wildcard + }, + { + "on_add_pair_pred_wildcard", + Trigger_on_add_pair_pred_wildcard + }, + { + "on_add_pair_wildcard", + Trigger_on_add_pair_wildcard + }, + { + "on_remove_tag", + Trigger_on_remove_tag + }, + { + "on_remove_component", + Trigger_on_remove_component + }, + { + "on_remove_wildcard", + Trigger_on_remove_wildcard + }, + { + "on_remove_pair", + Trigger_on_remove_pair + }, + { + "on_remove_pair_obj_wildcard", + Trigger_on_remove_pair_obj_wildcard + }, + { + "on_remove_pair_pred_wildcard", + Trigger_on_remove_pair_pred_wildcard + }, + { + "on_remove_pair_wildcard", + Trigger_on_remove_pair_wildcard + }, + { + "on_add_remove", + Trigger_on_add_remove + }, + { + "on_set_component", + Trigger_on_set_component + }, + { + "on_set_wildcard", + Trigger_on_set_wildcard + }, + { + "on_set_pair", + Trigger_on_set_pair + }, + { + "on_set_pair_w_obj_wildcard", + Trigger_on_set_pair_w_obj_wildcard + }, + { + "on_set_pair_pred_wildcard", + Trigger_on_set_pair_pred_wildcard + }, + { + "on_set_pair_wildcard", + Trigger_on_set_pair_wildcard + }, + { + "on_set_component_after_modified", + Trigger_on_set_component_after_modified + }, + { + "un_set_component", + Trigger_un_set_component + }, + { + "un_set_wildcard", + Trigger_un_set_wildcard + }, + { + "un_set_pair", + Trigger_un_set_pair + }, + { + "un_set_pair_w_obj_wildcard", + Trigger_un_set_pair_w_obj_wildcard + }, + { + "un_set_pair_pred_wildcard", + Trigger_un_set_pair_pred_wildcard + }, + { + "un_set_pair_wildcard", + Trigger_un_set_pair_wildcard + }, + { + "add_twice", + Trigger_add_twice + }, + { + "remove_twice", + Trigger_remove_twice + }, + { + "on_remove_w_clear", + Trigger_on_remove_w_clear + }, + { + "on_remove_w_delete", + Trigger_on_remove_w_delete + }, + { + "on_remove_w_world_fini", + Trigger_on_remove_w_world_fini + }, + { + "on_add_w_clone", + Trigger_on_add_w_clone + }, + { + "add_in_trigger", + Trigger_add_in_trigger + }, + { + "remove_in_trigger", + Trigger_remove_in_trigger + }, + { + "clear_in_trigger", + Trigger_clear_in_trigger + }, + { + "delete_in_trigger", + Trigger_delete_in_trigger + }, + { + "trigger_w_named_entity", + Trigger_trigger_w_named_entity + }, + { + "on_remove_tree", + Trigger_on_remove_tree + }, + { + "set_get_context", + Trigger_set_get_context + }, + { + "set_get_binding_context", + Trigger_set_get_binding_context + }, + { + "trigger_w_self", + Trigger_trigger_w_self + }, + { + "delete_trigger_w_delete_ctx", + Trigger_delete_trigger_w_delete_ctx + }, + { + "trigger_w_index", + Trigger_trigger_w_index + } +}; + +bake_test_case Observer_testcases[] = { + { + "2_terms_w_on_add", + Observer_2_terms_w_on_add + }, + { + "2_terms_w_on_remove", + Observer_2_terms_w_on_remove + }, + { + "2_terms_w_on_set_value", + Observer_2_terms_w_on_set_value + }, + { + "2_terms_w_on_remove_value", + Observer_2_terms_w_on_remove_value + }, + { + "2_terms_w_on_add_2nd", + Observer_2_terms_w_on_add_2nd + }, + { + "2_terms_w_on_remove_2nd", + Observer_2_terms_w_on_remove_2nd + }, + { + "2_pair_terms_w_on_add", + Observer_2_pair_terms_w_on_add + }, + { + "2_pair_terms_w_on_remove", + Observer_2_pair_terms_w_on_remove + }, + { + "2_wildcard_pair_terms_w_on_add", + Observer_2_wildcard_pair_terms_w_on_add + }, + { + "2_wildcard_pair_terms_w_on_add_2_matching", + Observer_2_wildcard_pair_terms_w_on_add_2_matching + }, + { + "2_wildcard_pair_terms_w_on_add_3_matching", + Observer_2_wildcard_pair_terms_w_on_add_3_matching + }, + { + "2_wildcard_pair_terms_w_on_remove", + Observer_2_wildcard_pair_terms_w_on_remove + }, + { + "2_terms_1_not_w_on_add", + Observer_2_terms_1_not_w_on_add + }, + { + "2_terms_1_not_w_on_remove", + Observer_2_terms_1_not_w_on_remove + }, + { + "2_terms_w_on_set", + Observer_2_terms_w_on_set + }, + { + "2_terms_w_un_set", + Observer_2_terms_w_un_set + }, + { + "3_terms_2_or_on_add", + Observer_3_terms_2_or_on_add + }, + { + "3_terms_2_or_on_remove", + Observer_3_terms_2_or_on_remove + }, + { + "2_terms_w_from_entity_on_add", + Observer_2_terms_w_from_entity_on_add + }, + { + "2_terms_on_remove_on_clear", + Observer_2_terms_on_remove_on_clear + }, + { + "2_terms_on_remove_on_delete", + Observer_2_terms_on_remove_on_delete + }, + { + "observer_w_self", + Observer_observer_w_self + }, + { + "add_after_delete_observer", + Observer_add_after_delete_observer + }, + { + "remove_after_delete_observer", + Observer_remove_after_delete_observer + }, + { + "delete_observer_w_ctx", + Observer_delete_observer_w_ctx + }, + { + "filter_w_strings", + Observer_filter_w_strings + } +}; + +bake_test_case TriggerOnAdd_testcases[] = { + { + "new_match_1_of_1", + TriggerOnAdd_new_match_1_of_1 + }, + { + "new_match_1_of_2", + TriggerOnAdd_new_match_1_of_2 + }, + { + "new_no_match_1", + TriggerOnAdd_new_no_match_1 + }, + { + "new_w_count_match_1_of_1", + TriggerOnAdd_new_w_count_match_1_of_1 + }, + { + "add_match_1_of_1", + TriggerOnAdd_add_match_1_of_1 + }, + { + "add_match_1_of_2", + TriggerOnAdd_add_match_1_of_2 + }, + { + "add_no_match_1", + TriggerOnAdd_add_no_match_1 + }, + { + "set_match_1_of_1", + TriggerOnAdd_set_match_1_of_1 + }, + { + "set_no_match_1", + TriggerOnAdd_set_no_match_1 + }, + { + "clone_match_1_of_1", + TriggerOnAdd_clone_match_1_of_1 + }, + { + "clone_match_1_of_2", + TriggerOnAdd_clone_match_1_of_2 + }, + { + "add_again_1", + TriggerOnAdd_add_again_1 + }, + { + "set_again_1", + TriggerOnAdd_set_again_1 + }, + { + "add_again_2", + TriggerOnAdd_add_again_2 + }, + { + "override_after_add_in_on_add", + TriggerOnAdd_override_after_add_in_on_add + }, + { + "add_again_in_progress", + TriggerOnAdd_add_again_in_progress + }, + { + "add_in_progress_before_system_def", + TriggerOnAdd_add_in_progress_before_system_def + }, + { + "2_systems_w_table_creation", + TriggerOnAdd_2_systems_w_table_creation + }, + { + "2_systems_w_table_creation_in_progress", + TriggerOnAdd_2_systems_w_table_creation_in_progress + }, + { + "sys_context", + TriggerOnAdd_sys_context + }, + { + "get_sys_context_from_param", + TriggerOnAdd_get_sys_context_from_param + }, + { + "remove_added_component_in_on_add_w_set", + TriggerOnAdd_remove_added_component_in_on_add_w_set + }, + { + "on_add_in_on_add", + TriggerOnAdd_on_add_in_on_add + }, + { + "on_remove_in_on_add", + TriggerOnAdd_on_remove_in_on_add + }, + { + "on_set_in_on_add", + TriggerOnAdd_on_set_in_on_add + }, + { + "on_add_in_on_update", + TriggerOnAdd_on_add_in_on_update + }, + { + "emplace", + TriggerOnAdd_emplace + }, + { + "add_after_delete_trigger", + TriggerOnAdd_add_after_delete_trigger + }, + { + "add_after_delete_wildcard_id_trigger", + TriggerOnAdd_add_after_delete_wildcard_id_trigger + } +}; + +bake_test_case TriggerOnRemove_testcases[] = { + { + "remove_match_1_of_1", + TriggerOnRemove_remove_match_1_of_1 + }, + { + "remove_match_1_of_2", + TriggerOnRemove_remove_match_1_of_2 + }, + { + "remove_no_match_1", + TriggerOnRemove_remove_no_match_1 + }, + { + "delete_match_1_of_1", + TriggerOnRemove_delete_match_1_of_1 + }, + { + "delete_match_1_of_2", + TriggerOnRemove_delete_match_1_of_2 + }, + { + "delete_no_match_1", + TriggerOnRemove_delete_no_match_1 + }, + { + "remove_watched", + TriggerOnRemove_remove_watched + }, + { + "delete_watched", + TriggerOnRemove_delete_watched + }, + { + "valid_entity_after_delete", + TriggerOnRemove_valid_entity_after_delete + }, + { + "remove_after_delete_trigger", + TriggerOnRemove_remove_after_delete_trigger + }, + { + "remove_after_delete_wildcard_id_trigger", + TriggerOnRemove_remove_after_delete_wildcard_id_trigger + }, + { + "has_removed_tag_trigger_1_tag", + TriggerOnRemove_has_removed_tag_trigger_1_tag + }, + { + "has_removed_tag_trigger_2_tags", + TriggerOnRemove_has_removed_tag_trigger_2_tags + } +}; + +bake_test_case TriggerOnSet_testcases[] = { + { + "set", + TriggerOnSet_set + }, + { + "set_new", + TriggerOnSet_set_new + }, + { + "set_again", + TriggerOnSet_set_again + }, + { + "clone", + TriggerOnSet_clone + }, + { + "clone_w_value", + TriggerOnSet_clone_w_value + }, + { + "set_and_add_system", + TriggerOnSet_set_and_add_system + }, + { + "on_set_after_override", + TriggerOnSet_on_set_after_override + }, + { + "on_set_after_override_w_new", + TriggerOnSet_on_set_after_override_w_new + }, + { + "on_set_after_override_w_new_w_count", + TriggerOnSet_on_set_after_override_w_new_w_count + }, + { + "on_set_after_override_1_of_2_overridden", + TriggerOnSet_on_set_after_override_1_of_2_overridden + }, + { + "on_set_after_snapshot_restore", + TriggerOnSet_on_set_after_snapshot_restore + }, + { + "emplace", + TriggerOnSet_emplace + } +}; + +bake_test_case Monitor_testcases[] = { + { + "1_comp", + Monitor_1_comp + }, + { + "2_comps", + Monitor_2_comps + }, + { + "1_comp_1_not", + Monitor_1_comp_1_not + }, + { + "1_parent", + Monitor_1_parent + }, + { + "1_comp_1_parent", + Monitor_1_comp_1_parent + }, + { + "1_comp_prefab_new", + Monitor_1_comp_prefab_new + }, + { + "1_comp_prefab_add", + Monitor_1_comp_prefab_add + } +}; + +bake_test_case SystemOnSet_testcases[] = { + { + "set_1_of_1", + SystemOnSet_set_1_of_1 + }, + { + "set_1_of_2", + SystemOnSet_set_1_of_2 + }, + { + "set_1_of_3", + SystemOnSet_set_1_of_3 + }, + { + "bulk_new_1", + SystemOnSet_bulk_new_1 + }, + { + "bulk_new_2", + SystemOnSet_bulk_new_2 + }, + { + "bulk_new_2_of_1", + SystemOnSet_bulk_new_2_of_1 + }, + { + "bulk_new_3", + SystemOnSet_bulk_new_3 + }, + { + "bulk_new_3_of_2", + SystemOnSet_bulk_new_3_of_2 + }, + { + "bulk_new_1_from_base", + SystemOnSet_bulk_new_1_from_base + }, + { + "set_1_of_2_1_from_base", + SystemOnSet_set_1_of_2_1_from_base + }, + { + "set_1_of_3_1_from_base", + SystemOnSet_set_1_of_3_1_from_base + }, + { + "add_base", + SystemOnSet_add_base + }, + { + "add_base_to_1_overridden", + SystemOnSet_add_base_to_1_overridden + }, + { + "add_base_to_2_overridden", + SystemOnSet_add_base_to_2_overridden + }, + { + "add_base_to_1_of_2_overridden", + SystemOnSet_add_base_to_1_of_2_overridden + }, + { + "on_set_after_remove_override", + SystemOnSet_on_set_after_remove_override + }, + { + "no_set_after_remove_base", + SystemOnSet_no_set_after_remove_base + }, + { + "un_set_after_remove", + SystemOnSet_un_set_after_remove + }, + { + "un_set_after_remove_base", + SystemOnSet_un_set_after_remove_base + }, + { + "add_to_current_in_on_set", + SystemOnSet_add_to_current_in_on_set + }, + { + "remove_from_current_in_on_set", + SystemOnSet_remove_from_current_in_on_set + }, + { + "remove_set_component_in_on_set", + SystemOnSet_remove_set_component_in_on_set + }, + { + "match_table_created_w_add_in_on_set", + SystemOnSet_match_table_created_w_add_in_on_set + }, + { + "set_optional", + SystemOnSet_set_optional + }, + { + "set_from_nothing", + SystemOnSet_set_from_nothing + }, + { + "add_null_type_in_on_set", + SystemOnSet_add_null_type_in_on_set + }, + { + "add_0_entity_in_on_set", + SystemOnSet_add_0_entity_in_on_set + }, + { + "on_set_prefab", + SystemOnSet_on_set_prefab + } +}; + +bake_test_case SystemUnSet_testcases[] = { + { + "unset_1_of_1", + SystemUnSet_unset_1_of_1 + }, + { + "unset_1_of_2", + SystemUnSet_unset_1_of_2 + }, + { + "unset_1_of_3", + SystemUnSet_unset_1_of_3 + }, + { + "unset_on_delete_1", + SystemUnSet_unset_on_delete_1 + }, + { + "unset_on_delete_2", + SystemUnSet_unset_on_delete_2 + }, + { + "unset_on_delete_3", + SystemUnSet_unset_on_delete_3 + }, + { + "unset_on_fini_1", + SystemUnSet_unset_on_fini_1 + }, + { + "unset_on_fini_2", + SystemUnSet_unset_on_fini_2 + }, + { + "unset_on_fini_3", + SystemUnSet_unset_on_fini_3 + }, + { + "overlapping_unset_systems", + SystemUnSet_overlapping_unset_systems + }, + { + "unset_move_to_nonempty_table", + SystemUnSet_unset_move_to_nonempty_table + }, + { + "write_in_unset", + SystemUnSet_write_in_unset + } +}; + +bake_test_case SystemPeriodic_testcases[] = { + { + "1_type_1_component", + SystemPeriodic_1_type_1_component + }, + { + "1_type_3_component", + SystemPeriodic_1_type_3_component + }, + { + "3_type_1_component", + SystemPeriodic_3_type_1_component + }, + { + "2_type_3_component", + SystemPeriodic_2_type_3_component + }, + { + "1_type_1_component_1_tag", + SystemPeriodic_1_type_1_component_1_tag + }, + { + "2_type_1_component_1_tag", + SystemPeriodic_2_type_1_component_1_tag + }, + { + "2_type_1_and_1_not", + SystemPeriodic_2_type_1_and_1_not + }, + { + "2_type_2_and_1_not", + SystemPeriodic_2_type_2_and_1_not + }, + { + "2_type_2_and_2_not", + SystemPeriodic_2_type_2_and_2_not + }, + { + "4_type_1_and_1_or", + SystemPeriodic_4_type_1_and_1_or + }, + { + "4_type_1_and_1_or_of_3", + SystemPeriodic_4_type_1_and_1_or_of_3 + }, + { + "1_type_1_and_1_or", + SystemPeriodic_1_type_1_and_1_or + }, + { + "2_type_1_and_1_optional", + SystemPeriodic_2_type_1_and_1_optional + }, + { + "2_type_2_and_1_optional", + SystemPeriodic_2_type_2_and_1_optional + }, + { + "6_type_1_and_2_optional", + SystemPeriodic_6_type_1_and_2_optional + }, + { + "ensure_optional_is_unset_column", + SystemPeriodic_ensure_optional_is_unset_column + }, + { + "ensure_optional_is_null_shared", + SystemPeriodic_ensure_optional_is_null_shared + }, + { + "match_2_systems_w_populated_table", + SystemPeriodic_match_2_systems_w_populated_table + }, + { + "on_period", + SystemPeriodic_on_period + }, + { + "on_period_long_delta", + SystemPeriodic_on_period_long_delta + }, + { + "disabled", + SystemPeriodic_disabled + }, + { + "disabled_feature", + SystemPeriodic_disabled_feature + }, + { + "disabled_nested_feature", + SystemPeriodic_disabled_nested_feature + }, + { + "two_refs", + SystemPeriodic_two_refs + }, + { + "filter_disabled", + SystemPeriodic_filter_disabled + }, + { + "match_disabled", + SystemPeriodic_match_disabled + }, + { + "match_disabled_and_enabled", + SystemPeriodic_match_disabled_and_enabled + }, + { + "match_prefab", + SystemPeriodic_match_prefab + }, + { + "match_prefab_and_normal", + SystemPeriodic_match_prefab_and_normal + }, + { + "is_shared_on_column_not_set", + SystemPeriodic_is_shared_on_column_not_set + }, + { + "owned_column", + SystemPeriodic_owned_column + }, + { + "owned_not_column", + SystemPeriodic_owned_not_column + }, + { + "owned_or_column", + SystemPeriodic_owned_or_column + }, + { + "shared_column", + SystemPeriodic_shared_column + }, + { + "shared_not_column", + SystemPeriodic_shared_not_column + }, + { + "shared_or_column", + SystemPeriodic_shared_or_column + }, + { + "container_dont_match_inheritance", + SystemPeriodic_container_dont_match_inheritance + }, + { + "cascade_dont_match_inheritance", + SystemPeriodic_cascade_dont_match_inheritance + }, + { + "not_from_entity", + SystemPeriodic_not_from_entity + }, + { + "sys_context", + SystemPeriodic_sys_context + }, + { + "get_sys_context_from_param", + SystemPeriodic_get_sys_context_from_param + }, + { + "owned_only", + SystemPeriodic_owned_only + }, + { + "shared_only", + SystemPeriodic_shared_only + }, + { + "is_in_readonly", + SystemPeriodic_is_in_readonly + }, + { + "get_period", + SystemPeriodic_get_period + }, + { + "and_type", + SystemPeriodic_and_type + }, + { + "or_type", + SystemPeriodic_or_type + } +}; + +bake_test_case Timer_testcases[] = { + { + "timeout", + Timer_timeout + }, + { + "interval", + Timer_interval + }, + { + "shared_timeout", + Timer_shared_timeout + }, + { + "shared_interval", + Timer_shared_interval + }, + { + "start_stop_one_shot", + Timer_start_stop_one_shot + }, + { + "start_stop_interval", + Timer_start_stop_interval + }, + { + "rate_filter", + Timer_rate_filter + }, + { + "rate_filter_w_rate_filter_src", + Timer_rate_filter_w_rate_filter_src + }, + { + "rate_filter_w_timer_src", + Timer_rate_filter_w_timer_src + }, + { + "rate_filter_with_empty_src", + Timer_rate_filter_with_empty_src + }, + { + "one_shot_timer_entity", + Timer_one_shot_timer_entity + }, + { + "interval_timer_entity", + Timer_interval_timer_entity + }, + { + "rate_entity", + Timer_rate_entity + }, + { + "nested_rate_entity", + Timer_nested_rate_entity + }, + { + "nested_rate_entity_empty_src", + Timer_nested_rate_entity_empty_src + }, + { + "naked_tick_entity", + Timer_naked_tick_entity + } +}; + +bake_test_case SystemOnDemand_testcases[] = { + { + "enable_out_after_in", + SystemOnDemand_enable_out_after_in + }, + { + "enable_in_after_out", + SystemOnDemand_enable_in_after_out + }, + { + "enable_out_after_in_2_out_1_in", + SystemOnDemand_enable_out_after_in_2_out_1_in + }, + { + "enable_out_after_in_1_out_2_in", + SystemOnDemand_enable_out_after_in_1_out_2_in + }, + { + "enable_in_after_out_2_out_1_in", + SystemOnDemand_enable_in_after_out_2_out_1_in + }, + { + "enable_in_after_out_1_out_2_in", + SystemOnDemand_enable_in_after_out_1_out_2_in + }, + { + "disable_after_disable_in", + SystemOnDemand_disable_after_disable_in + }, + { + "disable_after_disable_in_2_out_1_in", + SystemOnDemand_disable_after_disable_in_2_out_1_in + }, + { + "disable_after_disable_in_1_out_2_in", + SystemOnDemand_disable_after_disable_in_1_out_2_in + }, + { + "table_after_out", + SystemOnDemand_table_after_out + }, + { + "table_after_out_and_in", + SystemOnDemand_table_after_out_and_in + }, + { + "table_after_out_and_in_overlapping_columns", + SystemOnDemand_table_after_out_and_in_overlapping_columns + }, + { + "1_out_system_2_in_systems", + SystemOnDemand_1_out_system_2_in_systems + }, + { + "1_out_system_2_in_systems_different_columns", + SystemOnDemand_1_out_system_2_in_systems_different_columns + }, + { + "1_out_system_2_in_systems_overlapping_columns", + SystemOnDemand_1_out_system_2_in_systems_overlapping_columns + }, + { + "disable_after_inactive_in_system", + SystemOnDemand_disable_after_inactive_in_system + }, + { + "disable_after_2_inactive_in_systems", + SystemOnDemand_disable_after_2_inactive_in_systems + }, + { + "disable_after_2_inactive_in_systems_different_columns", + SystemOnDemand_disable_after_2_inactive_in_systems_different_columns + }, + { + "enable_2_output_1_input_system", + SystemOnDemand_enable_2_output_1_input_system + }, + { + "enable_2_output_1_input_system_different_columns", + SystemOnDemand_enable_2_output_1_input_system_different_columns + }, + { + "enable_2_output_1_input_system_overlapping_columns", + SystemOnDemand_enable_2_output_1_input_system_overlapping_columns + }, + { + "out_not_column", + SystemOnDemand_out_not_column + }, + { + "trigger_on_manual", + SystemOnDemand_trigger_on_manual + }, + { + "trigger_on_manual_not_column", + SystemOnDemand_trigger_on_manual_not_column + }, + { + "on_demand_task_w_from_entity", + SystemOnDemand_on_demand_task_w_from_entity + }, + { + "on_demand_task_w_not_from_entity", + SystemOnDemand_on_demand_task_w_not_from_entity + }, + { + "enable_after_user_disable", + SystemOnDemand_enable_after_user_disable + } +}; + +bake_test_case SystemCascade_testcases[] = { + { + "cascade_depth_1", + SystemCascade_cascade_depth_1 + }, + { + "cascade_depth_2", + SystemCascade_cascade_depth_2 + }, + { + "cascade_depth_2_new_syntax", + SystemCascade_cascade_depth_2_new_syntax + }, + { + "add_after_match", + SystemCascade_add_after_match + }, + { + "adopt_after_match", + SystemCascade_adopt_after_match + }, + { + "rematch_w_empty_table", + SystemCascade_rematch_w_empty_table + }, + { + "query_w_only_cascade", + SystemCascade_query_w_only_cascade + }, + { + "custom_relation_cascade_depth_1", + SystemCascade_custom_relation_cascade_depth_1 + }, + { + "custom_relation_cascade_depth_2", + SystemCascade_custom_relation_cascade_depth_2 + }, + { + "custom_relation_add_after_match", + SystemCascade_custom_relation_add_after_match + }, + { + "custom_relation_adopt_after_match", + SystemCascade_custom_relation_adopt_after_match + }, + { + "custom_relation_rematch_w_empty_table", + SystemCascade_custom_relation_rematch_w_empty_table + } +}; + +bake_test_case SystemManual_testcases[] = { + { + "1_type_1_component", + SystemManual_1_type_1_component + }, + { + "activate_status", + SystemManual_activate_status + }, + { + "no_automerge", + SystemManual_no_automerge + }, + { + "dont_run_w_unmatching_entity_query", + SystemManual_dont_run_w_unmatching_entity_query + } +}; + +bake_test_case Tasks_testcases[] = { + { + "no_components", + Tasks_no_components + }, + { + "one_tag", + Tasks_one_tag + }, + { + "from_system", + Tasks_from_system + }, + { + "tasks_in_phases", + Tasks_tasks_in_phases + } +}; + +bake_test_case Prefab_testcases[] = { + { + "new_w_prefab", + Prefab_new_w_prefab + }, + { + "new_w_count_prefab", + Prefab_new_w_count_prefab + }, + { + "new_w_type_w_prefab", + Prefab_new_w_type_w_prefab + }, + { + "add_prefab", + Prefab_add_prefab + }, + { + "add_type_w_prefab", + Prefab_add_type_w_prefab + }, + { + "remove_prefab_after_new", + Prefab_remove_prefab_after_new + }, + { + "remove_prefab_after_add", + Prefab_remove_prefab_after_add + }, + { + "override_component", + Prefab_override_component + }, + { + "override_remove_component", + Prefab_override_remove_component + }, + { + "override_2_of_3_components_1_self", + Prefab_override_2_of_3_components_1_self + }, + { + "new_type_w_1_override", + Prefab_new_type_w_1_override + }, + { + "new_type_w_2_overrides", + Prefab_new_type_w_2_overrides + }, + { + "add_type_w_1_overrides", + Prefab_add_type_w_1_overrides + }, + { + "add_type_w_2_overrides", + Prefab_add_type_w_2_overrides + }, + { + "get_ptr_prefab", + Prefab_get_ptr_prefab + }, + { + "iterate_w_prefab_shared", + Prefab_iterate_w_prefab_shared + }, + { + "match_entity_prefab_w_system_optional", + Prefab_match_entity_prefab_w_system_optional + }, + { + "prefab_in_system_expr", + Prefab_prefab_in_system_expr + }, + { + "dont_match_prefab", + Prefab_dont_match_prefab + }, + { + "new_w_count_w_override", + Prefab_new_w_count_w_override + }, + { + "override_2_components_different_size", + Prefab_override_2_components_different_size + }, + { + "ignore_prefab_parent_component", + Prefab_ignore_prefab_parent_component + }, + { + "match_table_created_in_progress", + Prefab_match_table_created_in_progress + }, + { + "prefab_w_1_child", + Prefab_prefab_w_1_child + }, + { + "prefab_w_2_children", + Prefab_prefab_w_2_children + }, + { + "prefab_w_grandchild", + Prefab_prefab_w_grandchild + }, + { + "prefab_tree_1_2_1", + Prefab_prefab_tree_1_2_1 + }, + { + "prefab_w_base_w_child", + Prefab_prefab_w_base_w_child + }, + { + "prefab_w_child_w_base", + Prefab_prefab_w_child_w_base + }, + { + "prefab_w_child_w_base_w_children", + Prefab_prefab_w_child_w_base_w_children + }, + { + "prefab_w_child_new_w_count", + Prefab_prefab_w_child_new_w_count + }, + { + "prefab_auto_override_child_component", + Prefab_prefab_auto_override_child_component + }, + { + "ignore_on_add", + Prefab_ignore_on_add + }, + { + "ignore_on_remove", + Prefab_ignore_on_remove + }, + { + "ignore_on_set", + Prefab_ignore_on_set + }, + { + "on_set_on_instance", + Prefab_on_set_on_instance + }, + { + "instantiate_in_progress", + Prefab_instantiate_in_progress + }, + { + "copy_from_prefab_in_progress", + Prefab_copy_from_prefab_in_progress + }, + { + "copy_from_prefab_first_instance_in_progress", + Prefab_copy_from_prefab_first_instance_in_progress + }, + { + "ref_after_realloc", + Prefab_ref_after_realloc + }, + { + "revalidate_ref_w_mixed_table_refs", + Prefab_revalidate_ref_w_mixed_table_refs + }, + { + "no_overwrite_on_2nd_add", + Prefab_no_overwrite_on_2nd_add + }, + { + "no_overwrite_on_2nd_add_in_progress", + Prefab_no_overwrite_on_2nd_add_in_progress + }, + { + "no_instantiate_on_2nd_add", + Prefab_no_instantiate_on_2nd_add + }, + { + "no_instantiate_on_2nd_add_in_progress", + Prefab_no_instantiate_on_2nd_add_in_progress + }, + { + "nested_prefab_in_progress_w_count", + Prefab_nested_prefab_in_progress_w_count + }, + { + "nested_prefab_in_progress_w_count_set_after_override", + Prefab_nested_prefab_in_progress_w_count_set_after_override + }, + { + "get_ptr_from_prefab_from_new_table_in_progress", + Prefab_get_ptr_from_prefab_from_new_table_in_progress + }, + { + "match_base", + Prefab_match_base + }, + { + "match_base_after_add_in_prev_phase", + Prefab_match_base_after_add_in_prev_phase + }, + { + "override_watched_prefab", + Prefab_override_watched_prefab + }, + { + "rematch_twice", + Prefab_rematch_twice + }, + { + "add_to_empty_base_in_system", + Prefab_add_to_empty_base_in_system + }, + { + "dont_inherit_disabled", + Prefab_dont_inherit_disabled + }, + { + "clone_after_inherit_in_on_add", + Prefab_clone_after_inherit_in_on_add + }, + { + "override_from_nested", + Prefab_override_from_nested + }, + { + "create_multiple_nested_w_on_set", + Prefab_create_multiple_nested_w_on_set + }, + { + "create_multiple_nested_w_on_set_in_progress", + Prefab_create_multiple_nested_w_on_set_in_progress + }, + { + "single_on_set_on_child_w_override", + Prefab_single_on_set_on_child_w_override + }, + { + "force_owned", + Prefab_force_owned + }, + { + "force_owned_2", + Prefab_force_owned_2 + }, + { + "force_owned_nested", + Prefab_force_owned_nested + }, + { + "force_owned_type", + Prefab_force_owned_type + }, + { + "force_owned_type_w_pair", + Prefab_force_owned_type_w_pair + }, + { + "prefab_instanceof_hierarchy", + Prefab_prefab_instanceof_hierarchy + }, + { + "override_tag", + Prefab_override_tag + }, + { + "empty_prefab", + Prefab_empty_prefab + }, + { + "instanceof_0", + Prefab_instanceof_0 + }, + { + "instantiate_empty_child_table", + Prefab_instantiate_empty_child_table + }, + { + "instantiate_emptied_child_table", + Prefab_instantiate_emptied_child_table + }, + { + "override_2_prefabs", + Prefab_override_2_prefabs + }, + { + "rematch_after_add_instanceof_to_parent", + Prefab_rematch_after_add_instanceof_to_parent + }, + { + "child_of_instance", + Prefab_child_of_instance + }, + { + "rematch_after_prefab_delete", + Prefab_rematch_after_prefab_delete + }, + { + "add_tag_w_low_id_to_instance", + Prefab_add_tag_w_low_id_to_instance + }, + { + "get_type_after_base_add", + Prefab_get_type_after_base_add + }, + { + "get_type_after_recycled_base_add", + Prefab_get_type_after_recycled_base_add + }, + { + "new_w_recycled_base", + Prefab_new_w_recycled_base + }, + { + "add_recycled_base", + Prefab_add_recycled_base + }, + { + "remove_recycled_base", + Prefab_remove_recycled_base + }, + { + "get_from_recycled_base", + Prefab_get_from_recycled_base + }, + { + "override_from_recycled_base", + Prefab_override_from_recycled_base + }, + { + "remove_override_from_recycled_base", + Prefab_remove_override_from_recycled_base + }, + { + "instantiate_tree_from_recycled_base", + Prefab_instantiate_tree_from_recycled_base + }, + { + "rematch_after_add_to_recycled_base", + Prefab_rematch_after_add_to_recycled_base + }, + { + "get_tag_from_2nd_base", + Prefab_get_tag_from_2nd_base + }, + { + "get_component_from_2nd_base", + Prefab_get_component_from_2nd_base + }, + { + "get_component_from_1st_base", + Prefab_get_component_from_1st_base + }, + { + "get_component_from_2nd_base_of_base", + Prefab_get_component_from_2nd_base_of_base + }, + { + "get_component_from_1st_base_of_base", + Prefab_get_component_from_1st_base_of_base + }, + { + "get_component_from_2nd_base_prefab_base", + Prefab_get_component_from_2nd_base_prefab_base + }, + { + "get_component_from_1st_base_prefab_base", + Prefab_get_component_from_1st_base_prefab_base + }, + { + "get_component_from_2nd_base_of_base_prefab_base", + Prefab_get_component_from_2nd_base_of_base_prefab_base + }, + { + "get_component_from_1st_base_of_base_prefab_base", + Prefab_get_component_from_1st_base_of_base_prefab_base + } +}; + +bake_test_case System_w_FromContainer_testcases[] = { + { + "1_column_from_container", + System_w_FromContainer_1_column_from_container + }, + { + "2_column_1_from_container", + System_w_FromContainer_2_column_1_from_container + }, + { + "3_column_2_from_container", + System_w_FromContainer_3_column_2_from_container + }, + { + "3_column_2_from_different_container", + System_w_FromContainer_3_column_2_from_different_container + }, + { + "2_column_1_from_container_w_not", + System_w_FromContainer_2_column_1_from_container_w_not + }, + { + "2_column_1_from_container_w_not_prefab", + System_w_FromContainer_2_column_1_from_container_w_not_prefab + }, + { + "3_column_1_from_comtainer_1_from_container_w_not", + System_w_FromContainer_3_column_1_from_comtainer_1_from_container_w_not + }, + { + "2_column_1_from_container_w_or", + System_w_FromContainer_2_column_1_from_container_w_or + }, + { + "select_same_from_container", + System_w_FromContainer_select_same_from_container + }, + { + "add_component_after_match", + System_w_FromContainer_add_component_after_match + }, + { + "add_component_after_match_and_rematch", + System_w_FromContainer_add_component_after_match_and_rematch + }, + { + "add_component_after_match_unmatch", + System_w_FromContainer_add_component_after_match_unmatch + }, + { + "add_component_after_match_unmatch_match", + System_w_FromContainer_add_component_after_match_unmatch_match + }, + { + "add_component_after_match_2_systems", + System_w_FromContainer_add_component_after_match_2_systems + }, + { + "add_component_in_progress_after_match", + System_w_FromContainer_add_component_in_progress_after_match + }, + { + "add_component_after_match_and_rematch_w_entity_type_expr", + System_w_FromContainer_add_component_after_match_and_rematch_w_entity_type_expr + }, + { + "add_component_after_match_and_rematch_w_entity_type_expr_in_progress", + System_w_FromContainer_add_component_after_match_and_rematch_w_entity_type_expr_in_progress + }, + { + "adopt_after_match", + System_w_FromContainer_adopt_after_match + }, + { + "new_child_after_match", + System_w_FromContainer_new_child_after_match + }, + { + "realloc_after_match", + System_w_FromContainer_realloc_after_match + } +}; + +bake_test_case System_w_FromId_testcases[] = { + { + "2_column_1_from_id", + System_w_FromId_2_column_1_from_id + }, + { + "3_column_2_from_id", + System_w_FromId_3_column_2_from_id + }, + { + "column_type", + System_w_FromId_column_type + } +}; + +bake_test_case System_w_FromSystem_testcases[] = { + { + "2_column_1_from_system", + System_w_FromSystem_2_column_1_from_system + }, + { + "3_column_2_from_system", + System_w_FromSystem_3_column_2_from_system + }, + { + "auto_add_tag", + System_w_FromSystem_auto_add_tag + } +}; + +bake_test_case System_w_FromEntity_testcases[] = { + { + "2_column_1_from_entity", + System_w_FromEntity_2_column_1_from_entity + }, + { + "task_from_entity", + System_w_FromEntity_task_from_entity + }, + { + "task_not_from_entity", + System_w_FromEntity_task_not_from_entity + } +}; + +bake_test_case World_testcases[] = { + { + "progress_w_0", + World_progress_w_0 + }, + { + "progress_w_t", + World_progress_w_t + }, + { + "get_tick", + World_get_tick + }, + { + "entity_range_offset", + World_entity_range_offset + }, + { + "entity_range_offset_out_of_range", + World_entity_range_offset_out_of_range + }, + { + "entity_range_limit_out_of_range", + World_entity_range_limit_out_of_range + }, + { + "entity_range_add_existing_in_progress", + World_entity_range_add_existing_in_progress + }, + { + "entity_range_add_in_range_in_progress", + World_entity_range_add_in_range_in_progress + }, + { + "entity_range_add_out_of_range_in_progress", + World_entity_range_add_out_of_range_in_progress + }, + { + "entity_range_out_of_range_check_disabled", + World_entity_range_out_of_range_check_disabled + }, + { + "entity_range_check_after_delete", + World_entity_range_check_after_delete + }, + { + "dim", + World_dim + }, + { + "dim_dim_type", + World_dim_dim_type + }, + { + "phases", + World_phases + }, + { + "phases_w_merging", + World_phases_w_merging + }, + { + "phases_match_in_create", + World_phases_match_in_create + }, + { + "measure_time", + World_measure_time + }, + { + "control_fps", + World_control_fps + }, + { + "control_fps_busy_system", + World_control_fps_busy_system + }, + { + "control_fps_busy_app", + World_control_fps_busy_app + }, + { + "control_fps_random_system", + World_control_fps_random_system + }, + { + "control_fps_random_app", + World_control_fps_random_app + }, + { + "measure_fps_vs_actual", + World_measure_fps_vs_actual + }, + { + "measure_delta_time_vs_actual", + World_measure_delta_time_vs_actual + }, + { + "system_time_scale", + World_system_time_scale + }, + { + "quit", + World_quit + }, + { + "get_delta_time", + World_get_delta_time + }, + { + "get_delta_time_auto", + World_get_delta_time_auto + }, + { + "recreate_world", + World_recreate_world + }, + { + "recreate_world_w_component", + World_recreate_world_w_component + }, + { + "no_threading", + World_no_threading + }, + { + "no_time", + World_no_time + }, + { + "is_entity_enabled", + World_is_entity_enabled + }, + { + "get_stats", + World_get_stats + }, + { + "ensure_empty_root", + World_ensure_empty_root + }, + { + "register_alias_twice_same_entity", + World_register_alias_twice_same_entity + }, + { + "register_alias_twice_different_entity", + World_register_alias_twice_different_entity + } +}; + +bake_test_case Type_testcases[] = { + { + "type_of_1_tostr", + Type_type_of_1_tostr + }, + { + "type_of_2_tostr", + Type_type_of_2_tostr + }, + { + "type_of_2_tostr_no_id", + Type_type_of_2_tostr_no_id + }, + { + "type_redefine", + Type_type_redefine + }, + { + "type_has", + Type_type_has + }, + { + "type_has_not", + Type_type_has_not + }, + { + "zero_type_has_not", + Type_zero_type_has_not + }, + { + "type_merge", + Type_type_merge + }, + { + "type_merge_overlap", + Type_type_merge_overlap + }, + { + "type_merge_overlap_one", + Type_type_merge_overlap_one + }, + { + "type_add", + Type_type_add + }, + { + "type_add_empty", + Type_type_add_empty + }, + { + "type_add_entity_again", + Type_type_add_entity_again + }, + { + "type_add_out_of_order", + Type_type_add_out_of_order + }, + { + "type_add_existing", + Type_type_add_existing + }, + { + "type_add_empty_existing", + Type_type_add_empty_existing + }, + { + "type_add_out_of_order_existing", + Type_type_add_out_of_order_existing + }, + { + "type_remove", + Type_type_remove + }, + { + "type_remove_empty", + Type_type_remove_empty + }, + { + "type_remove_non_existing", + Type_type_remove_non_existing + }, + { + "type_of_2_add", + Type_type_of_2_add + }, + { + "type_of_3_add_entity_again", + Type_type_of_3_add_entity_again + }, + { + "invalid_entity_type_expression", + Type_invalid_entity_type_expression + }, + { + "invalid_container_type_expression", + Type_invalid_container_type_expression + }, + { + "invalid_system_type_expression", + Type_invalid_system_type_expression + }, + { + "type_from_empty_entity", + Type_type_from_empty_entity + }, + { + "get_type", + Type_get_type + }, + { + "get_type_from_empty", + Type_get_type_from_empty + }, + { + "get_type_from_0", + Type_get_type_from_0 + }, + { + "entity_from_type", + Type_entity_from_type + }, + { + "entity_from_empty_type", + Type_entity_from_empty_type + }, + { + "entity_from_type_w_2_elements", + Type_entity_from_type_w_2_elements + }, + { + "type_from_entity", + Type_type_from_entity + }, + { + "type_from_empty", + Type_type_from_empty + }, + { + "type_from_0", + Type_type_from_0 + }, + { + "type_to_expr_1_comp", + Type_type_to_expr_1_comp + }, + { + "type_to_expr_2_comp", + Type_type_to_expr_2_comp + }, + { + "type_to_expr_instanceof", + Type_type_to_expr_instanceof + }, + { + "type_to_expr_childof", + Type_type_to_expr_childof + }, + { + "type_to_expr_pair", + Type_type_to_expr_pair + }, + { + "type_to_expr_pair_w_comp", + Type_type_to_expr_pair_w_comp + }, + { + "type_to_expr_scope", + Type_type_to_expr_scope + }, + { + "type_from_expr", + Type_type_from_expr + }, + { + "type_from_expr_scope", + Type_type_from_expr_scope + }, + { + "type_from_expr_digit", + Type_type_from_expr_digit + }, + { + "type_from_expr_instanceof", + Type_type_from_expr_instanceof + }, + { + "type_from_expr_childof", + Type_type_from_expr_childof + }, + { + "type_from_expr_pair", + Type_type_from_expr_pair + }, + { + "type_from_expr_pair_w_comp", + Type_type_from_expr_pair_w_comp + }, + { + "entity_str", + Type_entity_str + }, + { + "entity_path_str", + Type_entity_path_str + }, + { + "entity_instanceof_str", + Type_entity_instanceof_str + }, + { + "entity_childof_str", + Type_entity_childof_str + }, + { + "entity_pair_str", + Type_entity_pair_str + }, + { + "entity_switch_str", + Type_entity_switch_str + }, + { + "entity_case_str", + Type_entity_case_str + }, + { + "entity_and_str", + Type_entity_and_str + }, + { + "entity_or_str", + Type_entity_or_str + }, + { + "entity_xor_str", + Type_entity_xor_str + }, + { + "entity_not_str", + Type_entity_not_str + }, + { + "entity_str_small_buffer", + Type_entity_str_small_buffer + }, + { + "role_pair_str", + Type_role_pair_str + }, + { + "role_switch_str", + Type_role_switch_str + }, + { + "role_case_str", + Type_role_case_str + }, + { + "role_and_str", + Type_role_and_str + }, + { + "role_or_str", + Type_role_or_str + }, + { + "role_xor_str", + Type_role_xor_str + }, + { + "role_not_str", + Type_role_not_str + }, + { + "role_owned_str", + Type_role_owned_str + }, + { + "role_disabled_str", + Type_role_disabled_str + }, + { + "large_type_expr", + Type_large_type_expr + }, + { + "large_type_expr_limit", + Type_large_type_expr_limit + } +}; + +bake_test_case Run_testcases[] = { + { + "run", + Run_run + }, + { + "run_w_param", + Run_run_w_param + }, + { + "run_no_match", + Run_run_no_match + }, + { + "run_w_offset", + Run_run_w_offset + }, + { + "run_w_offset_skip_1_archetype", + Run_run_w_offset_skip_1_archetype + }, + { + "run_w_offset_skip_1_archetype_plus_one", + Run_run_w_offset_skip_1_archetype_plus_one + }, + { + "run_w_offset_skip_2_archetypes", + Run_run_w_offset_skip_2_archetypes + }, + { + "run_w_limit_skip_1_archetype", + Run_run_w_limit_skip_1_archetype + }, + { + "run_w_limit_skip_1_archetype_minus_one", + Run_run_w_limit_skip_1_archetype_minus_one + }, + { + "run_w_limit_skip_2_archetypes", + Run_run_w_limit_skip_2_archetypes + }, + { + "run_w_offset_1_limit_max", + Run_run_w_offset_1_limit_max + }, + { + "run_w_offset_1_limit_minus_1", + Run_run_w_offset_1_limit_minus_1 + }, + { + "run_w_offset_2_type_limit_max", + Run_run_w_offset_2_type_limit_max + }, + { + "run_w_offset_2_type_limit_minus_1", + Run_run_w_offset_2_type_limit_minus_1 + }, + { + "run_w_limit_1_all_offsets", + Run_run_w_limit_1_all_offsets + }, + { + "run_w_offset_out_of_bounds", + Run_run_w_offset_out_of_bounds + }, + { + "run_w_limit_out_of_bounds", + Run_run_w_limit_out_of_bounds + }, + { + "run_w_component_filter", + Run_run_w_component_filter + }, + { + "run_w_type_filter_of_2", + Run_run_w_type_filter_of_2 + }, + { + "run_w_container_filter", + Run_run_w_container_filter + }, + { + "run_comb_10_entities_1_type", + Run_run_comb_10_entities_1_type + }, + { + "run_comb_10_entities_2_types", + Run_run_comb_10_entities_2_types + }, + { + "run_w_interrupt", + Run_run_w_interrupt + }, + { + "run_staging", + Run_run_staging + } +}; + +bake_test_case MultiThread_testcases[] = { + { + "2_thread_1_entity", + MultiThread_2_thread_1_entity + }, + { + "2_thread_2_entity", + MultiThread_2_thread_2_entity + }, + { + "2_thread_5_entity", + MultiThread_2_thread_5_entity + }, + { + "2_thread_10_entity", + MultiThread_2_thread_10_entity + }, + { + "3_thread_1_entity", + MultiThread_3_thread_1_entity + }, + { + "3_thread_2_entity", + MultiThread_3_thread_2_entity + }, + { + "3_thread_5_entity", + MultiThread_3_thread_5_entity + }, + { + "3_thread_10_entity", + MultiThread_3_thread_10_entity + }, + { + "4_thread_1_entity", + MultiThread_4_thread_1_entity + }, + { + "4_thread_2_entity", + MultiThread_4_thread_2_entity + }, + { + "4_thread_5_entity", + MultiThread_4_thread_5_entity + }, + { + "4_thread_10_entity", + MultiThread_4_thread_10_entity + }, + { + "5_thread_1_entity", + MultiThread_5_thread_1_entity + }, + { + "5_thread_2_entity", + MultiThread_5_thread_2_entity + }, + { + "5_thread_5_entity", + MultiThread_5_thread_5_entity + }, + { + "5_thread_10_entity", + MultiThread_5_thread_10_entity + }, + { + "6_thread_1_entity", + MultiThread_6_thread_1_entity + }, + { + "6_thread_2_entity", + MultiThread_6_thread_2_entity + }, + { + "6_thread_5_entity", + MultiThread_6_thread_5_entity + }, + { + "6_thread_10_entity", + MultiThread_6_thread_10_entity + }, + { + "2_thread_test_combs_100_entity_w_next_worker", + MultiThread_2_thread_test_combs_100_entity_w_next_worker + }, + { + "2_thread_test_combs_100_entity", + MultiThread_2_thread_test_combs_100_entity + }, + { + "3_thread_test_combs_100_entity", + MultiThread_3_thread_test_combs_100_entity + }, + { + "4_thread_test_combs_100_entity", + MultiThread_4_thread_test_combs_100_entity + }, + { + "5_thread_test_combs_100_entity", + MultiThread_5_thread_test_combs_100_entity + }, + { + "6_thread_test_combs_100_entity", + MultiThread_6_thread_test_combs_100_entity + }, + { + "2_thread_test_combs_100_entity_2_types", + MultiThread_2_thread_test_combs_100_entity_2_types + }, + { + "3_thread_test_combs_100_entity_2_types", + MultiThread_3_thread_test_combs_100_entity_2_types + }, + { + "4_thread_test_combs_100_entity_2_types", + MultiThread_4_thread_test_combs_100_entity_2_types + }, + { + "5_thread_test_combs_100_entity_2_types", + MultiThread_5_thread_test_combs_100_entity_2_types + }, + { + "6_thread_test_combs_100_entity_2_types", + MultiThread_6_thread_test_combs_100_entity_2_types + }, + { + "change_thread_count", + MultiThread_change_thread_count + }, + { + "multithread_quit", + MultiThread_multithread_quit + }, + { + "schedule_w_tasks", + MultiThread_schedule_w_tasks + }, + { + "reactive_system", + MultiThread_reactive_system + }, + { + "fini_after_set_threads", + MultiThread_fini_after_set_threads + } +}; + +bake_test_case DeferredActions_testcases[] = { + { + "defer_new", + DeferredActions_defer_new + }, + { + "defer_bulk_new", + DeferredActions_defer_bulk_new + }, + { + "defer_bulk_new_w_data", + DeferredActions_defer_bulk_new_w_data + }, + { + "defer_bulk_new_w_data_pair", + DeferredActions_defer_bulk_new_w_data_pair + }, + { + "defer_bulk_new_two", + DeferredActions_defer_bulk_new_two + }, + { + "defer_bulk_new_w_data_two", + DeferredActions_defer_bulk_new_w_data_two + }, + { + "defer_add", + DeferredActions_defer_add + }, + { + "defer_add_two", + DeferredActions_defer_add_two + }, + { + "defer_remove", + DeferredActions_defer_remove + }, + { + "defer_remove_two", + DeferredActions_defer_remove_two + }, + { + "defer_set", + DeferredActions_defer_set + }, + { + "defer_delete", + DeferredActions_defer_delete + }, + { + "defer_twice", + DeferredActions_defer_twice + }, + { + "defer_twice_in_progress", + DeferredActions_defer_twice_in_progress + }, + { + "run_w_defer", + DeferredActions_run_w_defer + }, + { + "system_in_progress_w_defer", + DeferredActions_system_in_progress_w_defer + }, + { + "defer_get_mut_no_modify", + DeferredActions_defer_get_mut_no_modify + }, + { + "defer_get_mut_w_modify", + DeferredActions_defer_get_mut_w_modify + }, + { + "defer_modify", + DeferredActions_defer_modify + }, + { + "defer_set_pair", + DeferredActions_defer_set_pair + }, + { + "defer_clear", + DeferredActions_defer_clear + }, + { + "defer_add_after_delete", + DeferredActions_defer_add_after_delete + }, + { + "defer_set_after_delete", + DeferredActions_defer_set_after_delete + }, + { + "defer_get_mut_after_delete", + DeferredActions_defer_get_mut_after_delete + }, + { + "defer_get_mut_after_delete_2nd_to_last", + DeferredActions_defer_get_mut_after_delete_2nd_to_last + }, + { + "defer_add_child_to_deleted_parent", + DeferredActions_defer_add_child_to_deleted_parent + }, + { + "recreate_deleted_entity_while_deferred", + DeferredActions_recreate_deleted_entity_while_deferred + }, + { + "defer_add_to_recycled_id", + DeferredActions_defer_add_to_recycled_id + }, + { + "defer_add_to_recycled_id_w_role", + DeferredActions_defer_add_to_recycled_id_w_role + }, + { + "defer_add_to_recycled_relation", + DeferredActions_defer_add_to_recycled_relation + }, + { + "defer_add_to_recycled_object", + DeferredActions_defer_add_to_recycled_object + }, + { + "defer_add_to_recycled_object_childof", + DeferredActions_defer_add_to_recycled_object_childof + }, + { + "defer_add_to_deleted_id", + DeferredActions_defer_add_to_deleted_id + }, + { + "defer_add_to_deleted_id_w_role", + DeferredActions_defer_add_to_deleted_id_w_role + }, + { + "defer_add_to_deleted_relation", + DeferredActions_defer_add_to_deleted_relation + }, + { + "defer_add_to_deleted_object", + DeferredActions_defer_add_to_deleted_object + }, + { + "defer_add_to_deleted_object_childof", + DeferredActions_defer_add_to_deleted_object_childof + }, + { + "defer_delete_added_id", + DeferredActions_defer_delete_added_id + }, + { + "defer_delete_added_id_w_role", + DeferredActions_defer_delete_added_id_w_role + }, + { + "defer_delete_added_relation", + DeferredActions_defer_delete_added_relation + }, + { + "defer_delete_added_object", + DeferredActions_defer_delete_added_object + }, + { + "defer_delete_added_object_childof", + DeferredActions_defer_delete_added_object_childof + }, + { + "discard_add", + DeferredActions_discard_add + }, + { + "discard_remove", + DeferredActions_discard_remove + }, + { + "discard_add_two", + DeferredActions_discard_add_two + }, + { + "discard_remove_two", + DeferredActions_discard_remove_two + }, + { + "discard_child", + DeferredActions_discard_child + }, + { + "discard_child_w_add", + DeferredActions_discard_child_w_add + }, + { + "defer_return_value", + DeferredActions_defer_return_value + }, + { + "defer_get_mut_pair", + DeferredActions_defer_get_mut_pair + }, + { + "async_stage_add", + DeferredActions_async_stage_add + }, + { + "async_stage_add_twice", + DeferredActions_async_stage_add_twice + }, + { + "async_stage_remove", + DeferredActions_async_stage_remove + }, + { + "async_stage_clear", + DeferredActions_async_stage_clear + }, + { + "async_stage_delete", + DeferredActions_async_stage_delete + }, + { + "async_stage_new", + DeferredActions_async_stage_new + }, + { + "async_stage_no_get", + DeferredActions_async_stage_no_get + }, + { + "async_stage_readonly", + DeferredActions_async_stage_readonly + }, + { + "async_stage_is_async", + DeferredActions_async_stage_is_async + }, + { + "register_component_while_in_progress", + DeferredActions_register_component_while_in_progress + }, + { + "register_component_while_staged", + DeferredActions_register_component_while_staged + }, + { + "register_component_while_deferred", + DeferredActions_register_component_while_deferred + }, + { + "defer_enable", + DeferredActions_defer_enable + }, + { + "defer_disable", + DeferredActions_defer_disable + } +}; + +bake_test_case SingleThreadStaging_testcases[] = { + { + "new_empty", + SingleThreadStaging_new_empty + }, + { + "new_w_component", + SingleThreadStaging_new_w_component + }, + { + "new_w_type_of_2", + SingleThreadStaging_new_w_type_of_2 + }, + { + "new_empty_w_count", + SingleThreadStaging_new_empty_w_count + }, + { + "new_component_w_count", + SingleThreadStaging_new_component_w_count + }, + { + "new_type_w_count", + SingleThreadStaging_new_type_w_count + }, + { + "add_to_new_empty", + SingleThreadStaging_add_to_new_empty + }, + { + "2_add_to_new_empty", + SingleThreadStaging_2_add_to_new_empty + }, + { + "add_remove_same_to_new_empty", + SingleThreadStaging_add_remove_same_to_new_empty + }, + { + "add_remove_2_same_to_new_empty", + SingleThreadStaging_add_remove_2_same_to_new_empty + }, + { + "add_remove_same_to_new_w_component", + SingleThreadStaging_add_remove_same_to_new_w_component + }, + { + "2_add_1_remove_to_new_empty", + SingleThreadStaging_2_add_1_remove_to_new_empty + }, + { + "2_add_1_remove_same_to_new_empty", + SingleThreadStaging_2_add_1_remove_same_to_new_empty + }, + { + "clone", + SingleThreadStaging_clone + }, + { + "clone_w_value", + SingleThreadStaging_clone_w_value + }, + { + "add_to_current", + SingleThreadStaging_add_to_current + }, + { + "2_add_to_current", + SingleThreadStaging_2_add_to_current + }, + { + "remove_from_current", + SingleThreadStaging_remove_from_current + }, + { + "remove_2_from_current", + SingleThreadStaging_remove_2_from_current + }, + { + "add_remove_same_to_current", + SingleThreadStaging_add_remove_same_to_current + }, + { + "add_remove_same_existing_to_current", + SingleThreadStaging_add_remove_same_existing_to_current + }, + { + "remove_add_same_to_current", + SingleThreadStaging_remove_add_same_to_current + }, + { + "remove_add_same_existing_to_current", + SingleThreadStaging_remove_add_same_existing_to_current + }, + { + "add_remove_2_same_to_current", + SingleThreadStaging_add_remove_2_same_to_current + }, + { + "add_remove_2_same_existing_to_current", + SingleThreadStaging_add_remove_2_same_existing_to_current + }, + { + "remove_add_2_same_to_current", + SingleThreadStaging_remove_add_2_same_to_current + }, + { + "remove_add_2_same_existing_to_current", + SingleThreadStaging_remove_add_2_same_existing_to_current + }, + { + "add_remove_different_to_current", + SingleThreadStaging_add_remove_different_to_current + }, + { + "add_remove_add_same_to_current", + SingleThreadStaging_add_remove_add_same_to_current + }, + { + "2_add_1_remove_to_current", + SingleThreadStaging_2_add_1_remove_to_current + }, + { + "1_add_2_remove_to_current", + SingleThreadStaging_1_add_2_remove_to_current + }, + { + "delete_current", + SingleThreadStaging_delete_current + }, + { + "delete_even", + SingleThreadStaging_delete_even + }, + { + "delete_new_empty", + SingleThreadStaging_delete_new_empty + }, + { + "delete_new_w_component", + SingleThreadStaging_delete_new_w_component + }, + { + "set_current", + SingleThreadStaging_set_current + }, + { + "set_new_empty", + SingleThreadStaging_set_new_empty + }, + { + "set_new_w_component", + SingleThreadStaging_set_new_w_component + }, + { + "set_existing_new_w_component", + SingleThreadStaging_set_existing_new_w_component + }, + { + "set_new_after_add", + SingleThreadStaging_set_new_after_add + }, + { + "remove_after_set", + SingleThreadStaging_remove_after_set + }, + { + "delete_after_set", + SingleThreadStaging_delete_after_set + }, + { + "add_to_current_in_on_add", + SingleThreadStaging_add_to_current_in_on_add + }, + { + "remove_from_current_in_on_add", + SingleThreadStaging_remove_from_current_in_on_add + }, + { + "remove_added_component_in_on_add", + SingleThreadStaging_remove_added_component_in_on_add + }, + { + "match_table_created_in_progress", + SingleThreadStaging_match_table_created_in_progress + }, + { + "match_table_created_w_new_in_progress", + SingleThreadStaging_match_table_created_w_new_in_progress + }, + { + "match_table_created_w_new_in_on_set", + SingleThreadStaging_match_table_created_w_new_in_on_set + }, + { + "merge_table_w_container_added_in_progress", + SingleThreadStaging_merge_table_w_container_added_in_progress + }, + { + "merge_table_w_container_added_on_set", + SingleThreadStaging_merge_table_w_container_added_on_set + }, + { + "merge_table_w_container_added_on_set_reverse", + SingleThreadStaging_merge_table_w_container_added_on_set_reverse + }, + { + "merge_after_tasks", + SingleThreadStaging_merge_after_tasks + }, + { + "override_after_remove_in_progress", + SingleThreadStaging_override_after_remove_in_progress + }, + { + "get_parent_in_progress", + SingleThreadStaging_get_parent_in_progress + }, + { + "merge_once", + SingleThreadStaging_merge_once + }, + { + "clear_stage_after_merge", + SingleThreadStaging_clear_stage_after_merge + }, + { + "get_mutable", + SingleThreadStaging_get_mutable + }, + { + "get_mutable_from_main", + SingleThreadStaging_get_mutable_from_main + }, + { + "get_mutable_w_add", + SingleThreadStaging_get_mutable_w_add + }, + { + "on_add_after_new_type_in_progress", + SingleThreadStaging_on_add_after_new_type_in_progress + }, + { + "new_type_from_entity", + SingleThreadStaging_new_type_from_entity + }, + { + "existing_type_from_entity", + SingleThreadStaging_existing_type_from_entity + }, + { + "new_type_add", + SingleThreadStaging_new_type_add + }, + { + "existing_type_add", + SingleThreadStaging_existing_type_add + }, + { + "lock_table", + SingleThreadStaging_lock_table + }, + { + "recursive_lock_table", + SingleThreadStaging_recursive_lock_table + }, + { + "modify_after_lock", + SingleThreadStaging_modify_after_lock + }, + { + "get_empty_case_from_stage", + SingleThreadStaging_get_empty_case_from_stage + }, + { + "get_case_from_stage", + SingleThreadStaging_get_case_from_stage + }, + { + "get_object_from_stage", + SingleThreadStaging_get_object_from_stage + } +}; + +bake_test_case MultiThreadStaging_testcases[] = { + { + "2_threads_add_to_current", + MultiThreadStaging_2_threads_add_to_current + }, + { + "3_threads_add_to_current", + MultiThreadStaging_3_threads_add_to_current + }, + { + "4_threads_add_to_current", + MultiThreadStaging_4_threads_add_to_current + }, + { + "5_threads_add_to_current", + MultiThreadStaging_5_threads_add_to_current + }, + { + "6_threads_add_to_current", + MultiThreadStaging_6_threads_add_to_current + }, + { + "2_threads_on_add", + MultiThreadStaging_2_threads_on_add + }, + { + "new_w_count", + MultiThreadStaging_new_w_count + }, + { + "custom_thread_auto_merge", + MultiThreadStaging_custom_thread_auto_merge + }, + { + "custom_thread_manual_merge", + MultiThreadStaging_custom_thread_manual_merge + }, + { + "custom_thread_partial_manual_merge", + MultiThreadStaging_custom_thread_partial_manual_merge + } +}; + +bake_test_case Stresstests_testcases[] = { + { + "create_1m_set_two_components", + Stresstests_create_1m_set_two_components + }, + { + "create_delete_entity_random_components", + Stresstests_create_delete_entity_random_components + }, + { + "set_entity_random_components", + Stresstests_set_entity_random_components + }, + { + "create_delete_entity_random_components_staged", + Stresstests_create_delete_entity_random_components_staged + }, + { + "set_entity_random_components_staged", + Stresstests_set_entity_random_components_staged + }, + { + "create_delete_entity_random_components_2_threads", + Stresstests_create_delete_entity_random_components_2_threads + }, + { + "set_entity_random_components_2_threads", + Stresstests_set_entity_random_components_2_threads + }, + { + "create_delete_entity_random_components_6_threads", + Stresstests_create_delete_entity_random_components_6_threads + }, + { + "set_entity_random_components_6_threads", + Stresstests_set_entity_random_components_6_threads + }, + { + "create_delete_entity_random_components_12_threads", + Stresstests_create_delete_entity_random_components_12_threads + }, + { + "set_entity_random_components_12_threads", + Stresstests_set_entity_random_components_12_threads + }, + { + "create_2m_entities_1_comp", + Stresstests_create_2m_entities_1_comp + }, + { + "create_2m_entities_bulk_1_comp", + Stresstests_create_2m_entities_bulk_1_comp + }, + { + "add_1k_tags", + Stresstests_add_1k_tags + }, + { + "create_1m_set_two_components", + Stresstests_create_1m_set_two_components + } +}; + +bake_test_case Snapshot_testcases[] = { + { + "simple_snapshot", + Snapshot_simple_snapshot + }, + { + "snapshot_after_new", + Snapshot_snapshot_after_new + }, + { + "snapshot_after_delete", + Snapshot_snapshot_after_delete + }, + { + "snapshot_after_new_type", + Snapshot_snapshot_after_new_type + }, + { + "snapshot_after_add", + Snapshot_snapshot_after_add + }, + { + "snapshot_after_remove", + Snapshot_snapshot_after_remove + }, + { + "snapshot_w_include_filter", + Snapshot_snapshot_w_include_filter + }, + { + "snapshot_w_exclude_filter", + Snapshot_snapshot_w_exclude_filter + }, + { + "snapshot_w_filter_after_new", + Snapshot_snapshot_w_filter_after_new + }, + { + "snapshot_w_filter_after_delete", + Snapshot_snapshot_w_filter_after_delete + }, + { + "snapshot_free_empty", + Snapshot_snapshot_free_empty + }, + { + "snapshot_free", + Snapshot_snapshot_free + }, + { + "snapshot_free_filtered", + Snapshot_snapshot_free_filtered + }, + { + "snapshot_free_filtered_w_dtor", + Snapshot_snapshot_free_filtered_w_dtor + }, + { + "snapshot_activate_table_w_filter", + Snapshot_snapshot_activate_table_w_filter + }, + { + "snapshot_copy", + Snapshot_snapshot_copy + }, + { + "snapshot_get_ref_after_restore", + Snapshot_snapshot_get_ref_after_restore + }, + { + "new_after_snapshot", + Snapshot_new_after_snapshot + }, + { + "new_empty_after_snapshot", + Snapshot_new_empty_after_snapshot + }, + { + "add_after_snapshot", + Snapshot_add_after_snapshot + }, + { + "delete_after_snapshot", + Snapshot_delete_after_snapshot + }, + { + "set_after_snapshot", + Snapshot_set_after_snapshot + }, + { + "restore_recycled", + Snapshot_restore_recycled + }, + { + "snapshot_w_new_in_onset", + Snapshot_snapshot_w_new_in_onset + }, + { + "snapshot_w_new_in_onset_in_snapshot_table", + Snapshot_snapshot_w_new_in_onset_in_snapshot_table + }, + { + "snapshot_from_stage", + Snapshot_snapshot_from_stage + } +}; + +bake_test_case Modules_testcases[] = { + { + "simple_module", + Modules_simple_module + }, + { + "import_module_from_system", + Modules_import_module_from_system + }, + { + "import_again", + Modules_import_again + }, + { + "scoped_component", + Modules_scoped_component + }, + { + "scoped_tag", + Modules_scoped_tag + }, + { + "scoped_system", + Modules_scoped_system + }, + { + "scoped_entity", + Modules_scoped_entity + }, + { + "name_prefix_component", + Modules_name_prefix_component + }, + { + "name_prefix_tag", + Modules_name_prefix_tag + }, + { + "name_prefix_system", + Modules_name_prefix_system + }, + { + "name_prefix_entity", + Modules_name_prefix_entity + }, + { + "name_prefix_type", + Modules_name_prefix_type + }, + { + "name_prefix_prefab", + Modules_name_prefix_prefab + }, + { + "name_prefix_pipeline", + Modules_name_prefix_pipeline + }, + { + "name_prefix_trigger", + Modules_name_prefix_trigger + }, + { + "name_prefix_underscore", + Modules_name_prefix_underscore + }, + { + "lookup_by_symbol", + Modules_lookup_by_symbol + }, + { + "import_type", + Modules_import_type + }, + { + "nested_module", + Modules_nested_module + }, + { + "module_tag_on_namespace", + Modules_module_tag_on_namespace + } +}; + +bake_test_case DirectAccess_testcases[] = { + { + "get_table_from_str", + DirectAccess_get_table_from_str + }, + { + "get_table_from_type", + DirectAccess_get_table_from_type + }, + { + "insert_record", + DirectAccess_insert_record + }, + { + "insert_record_w_entity", + DirectAccess_insert_record_w_entity + }, + { + "table_count", + DirectAccess_table_count + }, + { + "find_column", + DirectAccess_find_column + }, + { + "get_column", + DirectAccess_get_column + }, + { + "get_empty_column", + DirectAccess_get_empty_column + }, + { + "set_column", + DirectAccess_set_column + }, + { + "delete_column", + DirectAccess_delete_column + }, + { + "delete_column_explicit", + DirectAccess_delete_column_explicit + }, + { + "delete_column_w_dtor", + DirectAccess_delete_column_w_dtor + }, + { + "copy_to", + DirectAccess_copy_to + }, + { + "copy_pod_to", + DirectAccess_copy_pod_to + }, + { + "move_to", + DirectAccess_move_to + }, + { + "copy_to_no_copy", + DirectAccess_copy_to_no_copy + }, + { + "move_to_no_move", + DirectAccess_move_to_no_move + }, + { + "find_record_not_exists", + DirectAccess_find_record_not_exists + }, + { + "get_entities_empty_table", + DirectAccess_get_entities_empty_table + }, + { + "get_records_empty_table", + DirectAccess_get_records_empty_table + }, + { + "get_column_empty_table", + DirectAccess_get_column_empty_table + }, + { + "delete_column_empty_table", + DirectAccess_delete_column_empty_table + }, + { + "get_record_column_empty_table", + DirectAccess_get_record_column_empty_table + }, + { + "has_module", + DirectAccess_has_module + } +}; + +bake_test_case Internals_testcases[] = { + { + "deactivate_table", + Internals_deactivate_table + }, + { + "activate_table", + Internals_activate_table + }, + { + "activate_deactivate_table", + Internals_activate_deactivate_table + }, + { + "activate_deactivate_reactive", + Internals_activate_deactivate_reactive + }, + { + "activate_deactivate_activate_other", + Internals_activate_deactivate_activate_other + }, + { + "no_double_system_table_after_merge", + Internals_no_double_system_table_after_merge + }, + { + "recreate_deleted_table", + Internals_recreate_deleted_table + }, + { + "create_65k_tables", + Internals_create_65k_tables + } +}; + +bake_test_case Error_testcases[] = { + { + "abort", + Error_abort + }, + { + "abort_w_param", + Error_abort_w_param + }, + { + "override_abort", + Error_override_abort + }, + { + "assert_true", + Error_assert_true + }, + { + "assert_false", + Error_assert_false + }, + { + "assert_false_w_param", + Error_assert_false_w_param + }, + { + "error_codes", + Error_error_codes + }, + { + "log_dbg", + Error_log_dbg + }, + { + "log_log", + Error_log_log + }, + { + "log_warning", + Error_log_warning + }, + { + "log_error", + Error_log_error + } +}; + +static bake_test_suite suites[] = { + { + "Entity", + NULL, + NULL, + 44, + Entity_testcases + }, + { + "New", + New_setup, + NULL, + 34, + New_testcases + }, + { + "New_w_Count", + NULL, + NULL, + 21, + New_w_Count_testcases + }, + { + "Add", + NULL, + NULL, + 37, + Add_testcases + }, + { + "Switch", + Switch_setup, + NULL, + 33, + Switch_testcases + }, + { + "EnabledComponents", + NULL, + NULL, + 37, + EnabledComponents_testcases + }, + { + "Remove", + NULL, + NULL, + 15, + Remove_testcases + }, + { + "Parser", + NULL, + NULL, + 108, + Parser_testcases + }, + { + "Plecs", + NULL, + NULL, + 11, + Plecs_testcases + }, + { + "GlobalComponentIds", + NULL, + NULL, + 7, + GlobalComponentIds_testcases + }, + { + "Hierarchies", + Hierarchies_setup, + NULL, + 82, + Hierarchies_testcases + }, + { + "Add_bulk", + NULL, + NULL, + 13, + Add_bulk_testcases + }, + { + "Remove_bulk", + NULL, + NULL, + 13, + Remove_bulk_testcases + }, + { + "Add_remove_bulk", + NULL, + NULL, + 4, + Add_remove_bulk_testcases + }, + { + "Has", + NULL, + NULL, + 21, + Has_testcases + }, + { + "Count", + NULL, + NULL, + 6, + Count_testcases + }, + { + "Get_component", + Get_component_setup, + NULL, + 10, + Get_component_testcases + }, + { + "Reference", + Reference_setup, + NULL, + 10, + Reference_testcases + }, + { + "Delete", + Delete_setup, + NULL, + 30, + Delete_testcases + }, + { + "OnDelete", + NULL, + NULL, + 50, + OnDelete_testcases + }, + { + "Delete_w_filter", + NULL, + NULL, + 13, + Delete_w_filter_testcases + }, + { + "Set", + NULL, + NULL, + 28, + Set_testcases + }, + { + "Lookup", + Lookup_setup, + NULL, + 26, + Lookup_testcases + }, + { + "Singleton", + NULL, + NULL, + 3, + Singleton_testcases + }, + { + "Clone", + NULL, + NULL, + 14, + Clone_testcases + }, + { + "ComponentLifecycle", + ComponentLifecycle_setup, + NULL, + 61, + ComponentLifecycle_testcases + }, + { + "Pipeline", + Pipeline_setup, + NULL, + 26, + Pipeline_testcases + }, + { + "SystemMisc", + SystemMisc_setup, + NULL, + 88, + SystemMisc_testcases + }, + { + "Sorting", + NULL, + NULL, + 30, + Sorting_testcases + }, + { + "Filter", + NULL, + NULL, + 50, + Filter_testcases + }, + { + "Query", + NULL, + NULL, + 46, + Query_testcases + }, + { + "Pairs", + NULL, + NULL, + 57, + Pairs_testcases + }, + { + "Trigger", + NULL, + NULL, + 49, + Trigger_testcases + }, + { + "Observer", + NULL, + NULL, + 26, + Observer_testcases + }, + { + "TriggerOnAdd", + TriggerOnAdd_setup, + NULL, + 29, + TriggerOnAdd_testcases + }, + { + "TriggerOnRemove", + NULL, + NULL, + 13, + TriggerOnRemove_testcases + }, + { + "TriggerOnSet", + NULL, + NULL, + 12, + TriggerOnSet_testcases + }, + { + "Monitor", + NULL, + NULL, + 7, + Monitor_testcases + }, + { + "SystemOnSet", + NULL, + NULL, + 28, + SystemOnSet_testcases + }, + { + "SystemUnSet", + NULL, + NULL, + 12, + SystemUnSet_testcases + }, + { + "SystemPeriodic", + NULL, + NULL, + 47, + SystemPeriodic_testcases + }, + { + "Timer", + NULL, + NULL, + 16, + Timer_testcases + }, + { + "SystemOnDemand", + NULL, + NULL, + 27, + SystemOnDemand_testcases + }, + { + "SystemCascade", + NULL, + NULL, + 12, + SystemCascade_testcases + }, + { + "SystemManual", + SystemManual_setup, + NULL, + 4, + SystemManual_testcases + }, + { + "Tasks", + NULL, + NULL, + 4, + Tasks_testcases + }, + { + "Prefab", + Prefab_setup, + NULL, + 94, + Prefab_testcases + }, + { + "System_w_FromContainer", + System_w_FromContainer_setup, + NULL, + 20, + System_w_FromContainer_testcases + }, + { + "System_w_FromId", + NULL, + NULL, + 3, + System_w_FromId_testcases + }, + { + "System_w_FromSystem", + NULL, + NULL, + 3, + System_w_FromSystem_testcases + }, + { + "System_w_FromEntity", + NULL, + NULL, + 3, + System_w_FromEntity_testcases + }, + { + "World", + World_setup, + NULL, + 37, + World_testcases + }, + { + "Type", + Type_setup, + NULL, + 72, + Type_testcases + }, + { + "Run", + Run_setup, + NULL, + 24, + Run_testcases + }, + { + "MultiThread", + MultiThread_setup, + NULL, + 36, + MultiThread_testcases + }, + { + "DeferredActions", + NULL, + NULL, + 64, + DeferredActions_testcases + }, + { + "SingleThreadStaging", + SingleThreadStaging_setup, + NULL, + 70, + SingleThreadStaging_testcases + }, + { + "MultiThreadStaging", + MultiThreadStaging_setup, + NULL, + 10, + MultiThreadStaging_testcases + }, + { + "Stresstests", + Stresstests_setup, + NULL, + 15, + Stresstests_testcases + }, + { + "Snapshot", + NULL, + NULL, + 26, + Snapshot_testcases + }, + { + "Modules", + Modules_setup, + NULL, + 20, + Modules_testcases + }, + { + "DirectAccess", + NULL, + NULL, + 24, + DirectAccess_testcases + }, + { + "Internals", + Internals_setup, + NULL, + 8, + Internals_testcases + }, + { + "Error", + Error_setup, + NULL, + 11, + Error_testcases + } +}; + +int main(int argc, char *argv[]) { + ut_init(argv[0]); + return bake_test_run("api", argc, argv, suites, 64); +} diff --git a/fggl/ecs2/flecs/test/api/src/util.c b/fggl/ecs2/flecs/test/api/src/util.c new file mode 100644 index 0000000000000000000000000000000000000000..b67431c3e45b245b186e183cf60d84c8e394943c --- /dev/null +++ b/fggl/ecs2/flecs/test/api/src/util.c @@ -0,0 +1,71 @@ +#include <api.h> + +void probe_system_w_ctx( + ecs_iter_t *it, + Probe *ctx) +{ + if (!ctx) { + return; + } + + ctx->param = it->param; + ctx->system = it->system; + ctx->self = it->self; + ctx->event = it->event; + ctx->event_id = it->event_id; + ctx->offset = 0; + ctx->column_count = it->column_count; + ctx->term_index = it->term_index; + + int i; + for (i = 0; i < ctx->column_count; i ++) { + ctx->c[ctx->invoked][i] = it->ids[i]; + ctx->s[ctx->invoked][i] = ecs_term_source(it, i + 1); + + ecs_id_t e = ecs_term_id(it, i + 1); + test_assert(e != 0); + } + + if (it->entities) { + ecs_entity_t *e = ecs_term(it, ecs_entity_t, 0); + if (e) { + test_assert(e != NULL); + test_assert(it->entities != NULL); + test_assert(it->entities == e); + + for (i = 0; i < it->count; i ++) { + ctx->e[i + ctx->count] = e[i]; + } + ctx->count += it->count; + } + } + + ctx->invoked ++; +} + +void probe_system( + ecs_iter_t *it) +{ + Probe *ctx = ecs_get_context(it->world); + probe_system_w_ctx(it, ctx); +} + +void probe_has_entity(Probe *probe, ecs_entity_t e) { + int i; + for (i = 0; i < probe->count; i ++) { + if (probe->e[i] == e) { + break; + } + } + + test_assert(i != probe->count); +} + +void install_test_abort() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.abort_ = test_abort; + ecs_os_set_api(&os_api); + + ecs_tracing_enable(-5); +} diff --git a/fggl/ecs2/flecs/test/bake_tests.bzl b/fggl/ecs2/flecs/test/bake_tests.bzl new file mode 100644 index 0000000000000000000000000000000000000000..969f6cb779f69256124d4e4d47de08e8c9ff7d10 --- /dev/null +++ b/fggl/ecs2/flecs/test/bake_tests.bzl @@ -0,0 +1,16 @@ + +def persuite_bake_tests(name, deps, suites, visibility=None): + suites_mangled = [s.partition(".")[0].rpartition("/")[2] for s in suites] + + for s in suites_mangled: + native.cc_test( + name = "{}-{}".format(name, s), + deps = deps, + visibility = visibility, + args = [s] + ) + + native.test_suite( + name = name, + tests = [":{}-{}".format(name, s) for s in suites_mangled] + ) diff --git a/fggl/ecs2/flecs/test/collections/.gitignore b/fggl/ecs2/flecs/test/collections/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33560686714c750d281191605c7dd92518608fa9 --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/.gitignore @@ -0,0 +1,4 @@ +.bake_cache +.DS_Store +.vscode +bin diff --git a/fggl/ecs2/flecs/test/collections/include/collections.h b/fggl/ecs2/flecs/test/collections/include/collections.h new file mode 100644 index 0000000000000000000000000000000000000000..e350157da10101aabf58278b7f3985793da7a7b5 --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/include/collections.h @@ -0,0 +1,16 @@ +#ifndef COLLECTIONS_H +#define COLLECTIONS_H + +/* This generated file contains includes for project dependencies */ +#include <collections/bake_config.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fggl/ecs2/flecs/test/collections/include/collections/bake_config.h b/fggl/ecs2/flecs/test/collections/include/collections/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..d8688c9df17107fe95f36ac8686db76e01449900 --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/include/collections/bake_config.h @@ -0,0 +1,28 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 COLLECTIONS_BAKE_CONFIG_H +#define COLLECTIONS_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> +#ifdef __BAKE__ +#include <bake_util.h> +#endif +#include <bake_test.h> + +#endif + diff --git a/fggl/ecs2/flecs/test/collections/project.json b/fggl/ecs2/flecs/test/collections/project.json new file mode 100644 index 0000000000000000000000000000000000000000..ca794d86556b9d146ce8efd2ea315b6d8a3854f2 --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/project.json @@ -0,0 +1,132 @@ +{ + "id": "collections", + "type": "application", + "value": { + "author": "Sander Mertens", + "description": "Test project for flecs", + "public": false, + "coverage": false, + "use": [ + "flecs" + ] + }, + "test": { + "testsuites": [{ + "id": "Vector", + "setup": true, + "testcases": [ + "free_empty", + "count", + "count_empty", + "get", + "get_first", + "get_last", + "get_last_from_empty", + "get_last_from_null", + "get_empty", + "get_out_of_bound", + "add_empty", + "add_resize", + "sort_rnd", + "sort_sorted", + "sort_empty", + "sort_null", + "size_of_null", + "set_size_smaller_than_count", + "pop_elements", + "pop_null", + "reclaim", + "grow", + "copy", + "copy_null", + "memory", + "memory_from_null", + "addn_to_null", + "addn_to_0_size", + "set_min_count", + "set_min_size", + "set_min_size_to_smaller" + ] + }, { + "id": "Queue", + "setup": true, + "testcases": [ + "free_empty", + "push", + "from_array", + "last" + ] + }, { + "id": "Map", + "setup": true, + "testcases": [ + "count", + "count_empty", + "set_overwrite", + "set_rehash", + "set_zero_buckets", + "get", + "get_all", + "get_empty", + "get_unknown", + "iter", + "iter_empty", + "iter_zero_buckets", + "iter_null", + "remove", + "remove_empty", + "remove_unknown", + "grow", + "set_size_0", + "ensure" + ] + }, { + "id": "Sparse", + "setup": true, + "testcases": [ + "add_1", + "add_1_to_empty", + "add_1_chunk_size_1", + "add_n", + "add_n_chunk_size_1", + "remove", + "remove_first", + "remove_last", + "remove_all", + "remove_all_n_chunks", + "clear_1", + "clear_empty", + "clear_n", + "clear_n_chunks", + "add_after_clear", + "memory_null", + "copy", + "restore", + "create_delete", + "create_delete_2", + "count_of_null", + "size_of_null", + "copy_null" + ] + }, { + "id": "Strbuf", + "setup": true, + "testcases": [ + "append", + "appendstr", + "appendstrn", + "appendstr_null", + "append_list", + "append_nested_list", + "large_str", + "empty_str", + "append_zerocopy", + "append_zerocopy_only", + "append_zerocopy_const", + "reset", + "merge", + "app_buffer" + ] + }] + } +} diff --git a/fggl/ecs2/flecs/test/collections/src/Map.c b/fggl/ecs2/flecs/test/collections/src/Map.c new file mode 100644 index 0000000000000000000000000000000000000000..51a0ef7e7b3412614530be7cd4cad3055d9a5063 --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/src/Map.c @@ -0,0 +1,311 @@ +#include <collections.h> + +struct elem { int hash; char *value; } elems[] = { + {1, "hello"}, + {2, "world"}, + {3, "foo"}, + {4, "bar"} +}; + +static +void fill_map( + ecs_map_t *map) +{ + int i, count = sizeof(elems) / sizeof(struct elem); + + for (i = 0; i < count; i ++) { + ecs_map_set(map, elems[i].hash, &elems[i].value); + } +} + +static int32_t malloc_count; + +static +void *test_malloc(ecs_size_t size) { + malloc_count ++; + return malloc(size); +} + +static +void *test_calloc(ecs_size_t size) { + malloc_count ++; + return calloc(1, size); +} + +static +void *test_realloc(void *old_ptr, ecs_size_t size) { + malloc_count ++; + return realloc(old_ptr, size); +} + + +void Map_setup() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.malloc_ = test_malloc; + os_api.calloc_ = test_calloc; + os_api.realloc_ = test_realloc; + ecs_os_set_api(&os_api); +} + +void Map_count() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + test_int(ecs_map_count(map), 4); + ecs_map_free(map); +} + +void Map_count_empty() { + ecs_map_t *map = ecs_map_new(char*, 16); + test_int(ecs_map_count(map), 0); + ecs_map_free(map); +} + +void Map_set_overwrite() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + char *value = ecs_map_get_ptr(map, char*, 1); + test_assert(value != NULL); + + ecs_map_set(map, 1, &(char*){"foobar"}); + + value = ecs_map_get_ptr(map, char*, 1); + test_assert(value != NULL); + test_str(value, "foobar"); + ecs_map_free(map); +} + +void Map_set_rehash() { + ecs_map_t *map = ecs_map_new(char*, 8); + fill_map(map); + + test_int(ecs_map_bucket_count(map), 16); + + int i; + for (i = 5; i < 16; i ++) { + ecs_map_set(map, i, &(char*){"zzz"}); + } + + test_int(ecs_map_bucket_count(map), 32); + test_str(ecs_map_get_ptr(map, char*, 1), "hello"); + test_str(ecs_map_get_ptr(map, char*, 2), "world"); + test_str(ecs_map_get_ptr(map, char*, 3), "foo"); + test_str(ecs_map_get_ptr(map, char*, 4), "bar"); + test_str(ecs_map_get_ptr(map, char*, 5), "zzz"); + test_str(ecs_map_get_ptr(map, char*, 6), "zzz"); + test_str(ecs_map_get_ptr(map, char*, 7), "zzz"); + + for (i = 1; i < 8; i ++) { + ecs_map_set(map, i + 1000000, &(char*){"yyy"}); + } + + test_int(ecs_map_bucket_count(map), 64); + test_str(ecs_map_get_ptr(map, char*, 1 + 1000000), "yyy"); + test_str(ecs_map_get_ptr(map, char*, 2 + 1000000), "yyy"); + test_str(ecs_map_get_ptr(map, char*, 3 + 1000000), "yyy"); + test_str(ecs_map_get_ptr(map, char*, 4 + 1000000), "yyy"); + test_str(ecs_map_get_ptr(map, char*, 5 + 1000000), "yyy"); + test_str(ecs_map_get_ptr(map, char*, 6 + 1000000), "yyy"); + test_str(ecs_map_get_ptr(map, char*, 7 + 1000000), "yyy"); + + ecs_map_free(map); +} + +void Map_set_size() { + ecs_map_t *map = ecs_map_new(char*, 8); + fill_map(map); + + test_int(ecs_map_bucket_count(map), 16); + test_int(ecs_map_count(map), 4); + + ecs_map_set_size(map, 16); + test_int(ecs_map_count(map), 4); + test_int(ecs_map_bucket_count(map), 32); + + ecs_map_free(map); +} + +void Map_set_zero_buckets() { + ecs_map_t *map = ecs_map_new(char*, 0); + ecs_map_set(map, 1, &(char*){"hello"}); + test_int(ecs_map_count(map), 1); + ecs_map_free(map); +} + +void Map_get() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + char *value = ecs_map_get_ptr(map, char*, 1); + test_assert(value != NULL); + test_str(value, "hello"); + ecs_map_free(map); +} + +void Map_get_all() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + + char *value = ecs_map_get_ptr(map, char*, 1); + test_assert(value != NULL); + test_str(value, "hello"); + + value = ecs_map_get_ptr(map, char*, 2); + test_assert(value != NULL); + test_str(value, "world"); + + value = ecs_map_get_ptr(map, char*, 3); + test_assert(value != NULL); + test_str(value, "foo"); + + value = ecs_map_get_ptr(map, char*, 4); + test_assert(value != NULL); + test_str(value, "bar"); + + ecs_map_free(map); +} + +void Map_get_empty() { + ecs_map_t *map = ecs_map_new(char*, 16); + char *value = ecs_map_get_ptr(map, char*, 1); + test_assert(value == NULL); + ecs_map_free(map); +} + +void Map_get_unknown() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + char *value = ecs_map_get_ptr(map, char*, 5); + test_assert(value == NULL); + ecs_map_free(map); +} + +void Map_iter() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + + ecs_map_iter_t it = ecs_map_iter(map); + test_str(ecs_map_next_ptr(&it, char*, NULL), "hello"); + test_str(ecs_map_next_ptr(&it, char*, NULL), "world"); + test_str(ecs_map_next_ptr(&it, char*, NULL), "foo"); + test_str(ecs_map_next_ptr(&it, char*, NULL), "bar"); + test_assert(ecs_map_next_ptr(&it, char*, NULL) == NULL); + + ecs_map_free(map); +} + +void Map_iter_empty() { + ecs_map_t *map = ecs_map_new(char*, 16); + ecs_map_iter_t it = ecs_map_iter(map); + test_assert(!ecs_map_next(&it, char*, NULL)); + ecs_map_free(map); +} + +void Map_iter_zero_buckets() { + ecs_map_t *map = ecs_map_new(char*, 0); + ecs_map_iter_t it = ecs_map_iter(map); + test_assert(!ecs_map_next(&it, char*, NULL)); + ecs_map_free(map); +} + +void Map_iter_null() { + ecs_map_iter_t it = ecs_map_iter(NULL); + test_assert(!ecs_map_next(&it, char*, NULL)); +} + + +void Map_remove() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + ecs_map_remove(map, 3); + test_assert(ecs_map_get(map, char*, 3) == NULL); + test_int(ecs_map_count(map), 3); + ecs_map_free(map); +} + +void Map_remove_empty() { + ecs_map_t *map = ecs_map_new(char*, 16); + ecs_map_remove(map, 3); + test_int(ecs_map_count(map), 0); + ecs_map_free(map); +} + +void Map_remove_empty_no_buckets() { + ecs_map_t *map = ecs_map_new(char*, 0); + ecs_map_remove(map, 3); + test_int(ecs_map_count(map), 0); + ecs_map_free(map); +} + +void Map_remove_unknown() { + ecs_map_t *map = ecs_map_new(char*, 16); + fill_map(map); + ecs_map_remove(map, 5); + test_str(ecs_map_get_ptr(map, char*, 1), "hello"); + test_str(ecs_map_get_ptr(map, char*, 2), "world"); + test_str(ecs_map_get_ptr(map, char*, 3), "foo"); + test_str(ecs_map_get_ptr(map, char*, 4), "bar"); + test_int(ecs_map_count(map), 4); + ecs_map_free(map); +} + +void Map_remove_1_from_n_in_bucket() { + ecs_map_t *map = ecs_map_new(char*, 8); + + ecs_map_set(map, 0, &(char*){"hello"}); + ecs_map_set(map, 16, &(char*){"world"}); + ecs_map_set(map, 32, &(char*){"foo"}); + ecs_map_remove(map, 16); + test_int(ecs_map_count(map), 2); + test_str(ecs_map_get_ptr(map, char*, 0), "hello"); + test_str(ecs_map_get_ptr(map, char*, 32), "foo"); + ecs_map_free(map); +} + +void Map_remove_from_empty_bucket() { + ecs_map_t *map = ecs_map_new(char*, 8); + + ecs_map_set(map, 0, &(char*){"hello"}); + ecs_map_remove(map, 0); + ecs_map_remove(map, 0); + test_int(ecs_map_count(map), 0); + ecs_map_free(map); +} + +void Map_grow() { + ecs_map_t *map = ecs_map_new(char*, 1); + + ecs_map_grow(map, 10); + + malloc_count = 0; + + const char *v = "foo"; + int i; + for(i = 0; i < 10; i ++) { + ecs_map_set(map, i, &v); + } + + test_int(malloc_count, 20); + + ecs_map_free(map); +} + +void Map_set_size_0() { + ecs_map_t *map = ecs_map_new(char*, 1); + + ecs_map_set_size(map, 0); + + test_int( ecs_map_count(map), 0); + + ecs_map_free(map); +} + +void Map_ensure() { + ecs_map_t *map = ecs_map_new(char*, 1); + + char **ptr = ecs_map_ensure(map, char*, 2); + test_assert(ptr != NULL); + + test_int( ecs_map_count(map), 1); + + ecs_map_free(map); +} diff --git a/fggl/ecs2/flecs/test/collections/src/Queue.c b/fggl/ecs2/flecs/test/collections/src/Queue.c new file mode 100644 index 0000000000000000000000000000000000000000..b14660618050ac9848f2840c54f422cc376cc9ed --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/src/Queue.c @@ -0,0 +1,74 @@ +#include <collections.h> + +void Queue_setup() { + ecs_os_set_api_defaults(); +} + +void Queue_free_empty() { + ecs_queue_t *queue = ecs_queue_new(int, 3); + test_assert(queue != NULL); + ecs_queue_free(queue); +} + +void Queue_push() { + ecs_queue_t *queue = ecs_queue_new(int, 3); + test_assert(queue != NULL); + test_int(ecs_queue_count(queue), 0); + test_int(ecs_queue_index(queue), 0); + + int * + v = ecs_queue_push(queue, int); + test_assert(v != NULL); + *v = 10; + + test_int(ecs_queue_count(queue), 1); + test_int(ecs_queue_index(queue), 1); + test_int(*ecs_queue_get(queue, int, 0), 10); + + v = ecs_queue_push(queue, int); + test_assert(v != NULL); + *v = 20; + + test_int(ecs_queue_count(queue), 2); + test_int(ecs_queue_index(queue), 2); + test_int(*ecs_queue_get(queue, int, 0), 10); + + v = ecs_queue_push(queue, int); + test_assert(v != NULL); + *v = 30; + + test_int(ecs_queue_count(queue), 3); + test_int(ecs_queue_index(queue), 0); + test_int(*ecs_queue_get(queue, int, 0), 10); + + v = ecs_queue_push(queue, int); + *v = 40; + + test_int(ecs_queue_count(queue), 3); + test_int(ecs_queue_index(queue), 1); + test_int(*ecs_queue_get(queue, int, 0), 20); + + ecs_queue_free(queue); +} + +void Queue_from_array() { + int ints[] = {10, 20, 30}; + ecs_queue_t *queue = ecs_queue_from_array(int, 3, ints); + test_int(ecs_queue_count(queue), 3); + + test_int(*ecs_queue_get(queue, int, 0), 10); + test_int(*ecs_queue_get(queue, int, 1), 20); + test_int(*ecs_queue_get(queue, int, 2), 30); + + ecs_queue_free(queue); +} + +void Queue_last() { + int ints[] = {10, 20, 30}; + ecs_queue_t *queue = ecs_queue_from_array(int, 3, ints); + test_int(ecs_queue_count(queue), 3); + + test_int(*ecs_queue_last(queue, int), 30); + + ecs_queue_free(queue); +} diff --git a/fggl/ecs2/flecs/test/collections/src/Sparse.c b/fggl/ecs2/flecs/test/collections/src/Sparse.c new file mode 100644 index 0000000000000000000000000000000000000000..8db623bf11368870771bb2769685333e0b4021ff --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/src/Sparse.c @@ -0,0 +1,401 @@ +#include <collections.h> +#include <flecs/private/sparse.h> + +void Sparse_setup() { + ecs_os_set_api_defaults(); +} + +static +void populate(ecs_sparse_t *sp, int count) { + int prev_count = flecs_sparse_count(sp); + int i; + for (i = 0; i < count; i ++) { + int *elem = flecs_sparse_add(sp, int); + test_assert(elem != NULL); + *elem = i; + } + + test_int(flecs_sparse_count(sp), count + prev_count); +} + +void Sparse_add_1() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + int* elem = flecs_sparse_add(sp, int); + test_assert(elem != NULL); + test_int(flecs_sparse_count(sp), 1); + + int *ptr = flecs_sparse_get_dense(sp, int, 0); + test_assert(ptr != NULL); + test_assert(elem == ptr); + + flecs_sparse_free(sp); +} + +void Sparse_add_1_to_empty() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + int* elem = flecs_sparse_add(sp, int); + test_assert(elem != NULL); + test_int(flecs_sparse_count(sp), 1); + + int *ptr = flecs_sparse_get_dense(sp, int, 0); + test_assert(ptr != NULL); + test_assert(elem == ptr); + + flecs_sparse_free(sp); +} + +void Sparse_add_1_chunk_size_1() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + int* elem = flecs_sparse_add(sp, int); + test_assert(elem != NULL); + test_int(flecs_sparse_count(sp), 1); + + int *ptr = flecs_sparse_get_dense(sp, int, 0); + test_assert(ptr != NULL); + test_assert(elem == ptr); + + flecs_sparse_free(sp); +} + +void Sparse_add_n() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + int* elem1 = flecs_sparse_add(sp, int); + test_assert(elem1 != NULL); + test_int(flecs_sparse_count(sp), 1); + + int* elem2 = flecs_sparse_add(sp, int); + test_assert(elem2 != NULL); + test_int(flecs_sparse_count(sp), 2); + + int *ptr = flecs_sparse_get_dense(sp, int, 0); + test_assert(ptr != NULL); + test_assert(elem1 == ptr); + + ptr = flecs_sparse_get_dense(sp, int, 1); + test_assert(ptr != NULL); + test_assert(elem2 == ptr); + + flecs_sparse_free(sp); +} + +void Sparse_add_n_chunk_size_1() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + int* elem1 = flecs_sparse_add(sp, int); + test_assert(elem1 != NULL); + test_int(flecs_sparse_count(sp), 1); + + int* elem2 = flecs_sparse_add(sp, int); + test_assert(elem2 != NULL); + test_int(flecs_sparse_count(sp), 2); + + int *ptr = flecs_sparse_get_dense(sp, int, 0); + test_assert(ptr != NULL); + test_assert(elem1 == ptr); + + ptr = flecs_sparse_get_dense(sp, int, 1); + test_assert(ptr != NULL); + test_assert(elem2 == ptr); + + flecs_sparse_free(sp); +} + +void Sparse_remove() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 3); + + const uint64_t *indices = flecs_sparse_ids(sp); + uint64_t i0 = indices[0]; + uint64_t i1 = indices[2]; + + flecs_sparse_remove(sp, 1); + test_int(flecs_sparse_count(sp), 2); + + test_assert(flecs_sparse_get_dense(sp, int, 0) == flecs_sparse_get(sp, int, i0)); + test_assert(flecs_sparse_get_dense(sp, int, 1) == flecs_sparse_get(sp, int, i1)); + + flecs_sparse_free(sp); +} + +void Sparse_remove_first() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 3); + + const uint64_t *indices = flecs_sparse_ids(sp); + uint64_t i0 = indices[1]; + uint64_t i1 = indices[2]; + + flecs_sparse_remove(sp, 0); + test_int(flecs_sparse_count(sp), 2); + + test_assert(flecs_sparse_get_dense(sp, int, 0) == flecs_sparse_get(sp, int, i1)); + test_assert(flecs_sparse_get_dense(sp, int, 1) == flecs_sparse_get(sp, int, i0)); + + flecs_sparse_free(sp); +} + +void Sparse_remove_last() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 3); + + const uint64_t *indices = flecs_sparse_ids(sp); + uint64_t i0 = indices[0]; + uint64_t i1 = indices[1]; + + flecs_sparse_remove(sp, 2); + test_int(flecs_sparse_count(sp), 2); + + test_assert(flecs_sparse_get_dense(sp, int, 0) == flecs_sparse_get(sp, int, i0)); + test_assert(flecs_sparse_get_dense(sp, int, 1) == flecs_sparse_get(sp, int, i1)); + + flecs_sparse_free(sp); +} + +void Sparse_remove_all() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 3); + + void *elem2 = flecs_sparse_get_dense(sp, int, 1); + void *elem3 = flecs_sparse_get_dense(sp, int, 2); + + flecs_sparse_remove(sp, 0); + test_int(flecs_sparse_count(sp), 2); + test_assert(flecs_sparse_get(sp, int, 0) == NULL); + test_assert(flecs_sparse_get(sp, int, 1) == elem2); + test_assert(flecs_sparse_get(sp, int, 2) == elem3); + + flecs_sparse_remove(sp, 1); + test_int(flecs_sparse_count(sp), 1); + test_assert(flecs_sparse_get(sp, int, 0) == NULL); + test_assert(flecs_sparse_get(sp, int, 1) == NULL); + test_assert(flecs_sparse_get(sp, int, 2) == elem3); + + flecs_sparse_remove(sp, 2); + test_int(flecs_sparse_count(sp), 0); + test_assert(flecs_sparse_get(sp, int, 0) == NULL); + test_assert(flecs_sparse_get(sp, int, 1) == NULL); + test_assert(flecs_sparse_get(sp, int, 2) == NULL); + + flecs_sparse_free(sp); +} + +void Sparse_remove_all_n_chunks() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 128); + + int i; + for (i = 0; i < 128; i ++) { + flecs_sparse_remove(sp, i); + test_int(flecs_sparse_count(sp), 127 - i); + } + + flecs_sparse_free(sp); +} + +void Sparse_clear_1() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 1); + + flecs_sparse_clear(sp); + test_int(flecs_sparse_count(sp), 0); + + flecs_sparse_free(sp); +} + +void Sparse_clear_empty() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + flecs_sparse_clear(sp); + test_int(flecs_sparse_count(sp), 0); + + flecs_sparse_free(sp); +} + +void Sparse_clear_n() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 3); + + flecs_sparse_clear(sp); + test_int(flecs_sparse_count(sp), 0); + + flecs_sparse_free(sp); +} + +void Sparse_clear_n_chunks() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 128); + + flecs_sparse_clear(sp); + test_int(flecs_sparse_count(sp), 0); + + flecs_sparse_free(sp); +} + +void Sparse_memory_null() { + int32_t allocd = 0, used = 0; + flecs_sparse_memory(NULL, &allocd, &used); + test_int(allocd, 0); + test_int(used, 0); +} + +void Sparse_copy() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 128); + test_int(flecs_sparse_count(sp), 128); + + ecs_sparse_t *sp2 = flecs_sparse_copy(sp); + flecs_sparse_free(sp); + test_int(flecs_sparse_count(sp2), 128); + + int i; + for (i = 0; i < 128; i ++) { + test_int(*flecs_sparse_get(sp2, int, i), i); + } + + flecs_sparse_free(sp2); +} + +void Sparse_restore() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 128); + test_int(flecs_sparse_count(sp), 128); + + ecs_sparse_t *sp2 = flecs_sparse_copy(sp); + test_int(flecs_sparse_count(sp2), 128); + + populate(sp, 128); + test_int(flecs_sparse_count(sp), 256); + + flecs_sparse_restore(sp, sp2); + test_int(flecs_sparse_count(sp), 128); + + int i; + for (i = 0; i < 128; i ++) { + test_int(*flecs_sparse_get(sp, int, i), i); + } + + flecs_sparse_free(sp); + flecs_sparse_free(sp2); +} + +void Sparse_add_after_clear() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + populate(sp, 1); + + flecs_sparse_clear(sp); + test_int(flecs_sparse_count(sp), 0); + + int* elem = flecs_sparse_add(sp, int); + test_assert(elem != NULL); + *elem = 10; + + const uint64_t *indices = flecs_sparse_ids(sp); + test_assert(indices != NULL); + + int *ptr = flecs_sparse_get(sp, int, indices[0]); + test_assert(ptr != NULL); + test_assert(ptr == elem); + test_int(*ptr, 10); + + flecs_sparse_free(sp); +} + +void Sparse_create_delete() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + uint64_t id = flecs_sparse_new_id(sp); + test_int(flecs_sparse_count(sp), 1); + + flecs_sparse_remove(sp, id); + test_int(flecs_sparse_count(sp), 0); + test_assert(!flecs_sparse_is_alive(sp, id)); + + flecs_sparse_free(sp); +} + +void Sparse_create_delete_2() { + ecs_sparse_t *sp = flecs_sparse_new(int); + test_assert(sp != NULL); + test_int(flecs_sparse_count(sp), 0); + + uint64_t id_1 = flecs_sparse_new_id(sp); + test_int(flecs_sparse_count(sp), 1); + test_assert(flecs_sparse_is_alive(sp, id_1)); + + uint64_t id_2 = flecs_sparse_new_id(sp); + test_int(flecs_sparse_count(sp), 2); + test_assert(flecs_sparse_is_alive(sp, id_2)); + + flecs_sparse_remove(sp, id_1); + test_int(flecs_sparse_count(sp), 1); + test_assert(!flecs_sparse_is_alive(sp, id_1)); + + flecs_sparse_remove(sp, id_2); + test_int(flecs_sparse_count(sp), 0); + test_assert(!flecs_sparse_is_alive(sp, id_2)); + + flecs_sparse_free(sp); +} + +void Sparse_count_of_null() { + test_int(flecs_sparse_count(NULL), 0); +} + +void Sparse_size_of_null() { + test_int(flecs_sparse_size(NULL), 0); +} + +void Sparse_copy_null() { + test_assert(flecs_sparse_copy(NULL) == NULL); +} diff --git a/fggl/ecs2/flecs/test/collections/src/Strbuf.c b/fggl/ecs2/flecs/test/collections/src/Strbuf.c new file mode 100644 index 0000000000000000000000000000000000000000..3492612cff7030ed9681114cd919a25014abe31f --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/src/Strbuf.c @@ -0,0 +1,178 @@ +#include <collections.h> + +void Strbuf_setup() { + ecs_os_set_api_defaults(); +} + +void Strbuf_append() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_append(&b, "Foo"); + ecs_strbuf_append(&b, "Bar %d", 10); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "FooBar 10"); + ecs_os_free(str); +} + +void Strbuf_appendstr() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendstr(&b, "Foo"); + ecs_strbuf_appendstr(&b, "Bar"); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "FooBar"); + ecs_os_free(str); +} + +void Strbuf_appendstrn() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_append(&b, "Foo"); + ecs_strbuf_appendstrn(&b, "Bar", 1); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "FooB"); + ecs_os_free(str); +} + +void Strbuf_appendstr_null() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_append(&b, "Foo"); + ecs_strbuf_appendstr(&b, NULL); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "Foo"); + ecs_os_free(str); +} + +void Strbuf_append_list() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_append(&b, "Foo"); + ecs_strbuf_list_push(&b, "{", ","); + ecs_strbuf_list_append(&b, "Foo %d", 10); + ecs_strbuf_list_appendstr(&b, "Bar"); + ecs_strbuf_list_pop(&b, "}"); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "Foo{Foo 10,Bar}"); + ecs_os_free(str); +} + +void Strbuf_append_nested_list() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_append(&b, "Foo"); + ecs_strbuf_list_push(&b, "{", ","); + ecs_strbuf_list_append(&b, "Foo %d", 10); + ecs_strbuf_list_appendstr(&b, "Bar"); + + ecs_strbuf_list_push(&b, "[", ","); + ecs_strbuf_list_appendstr(&b, "Hello"); + ecs_strbuf_list_appendstr(&b, "World"); + ecs_strbuf_list_pop(&b, "]"); + + ecs_strbuf_list_next(&b); + ecs_strbuf_list_push(&b, "[", ","); + ecs_strbuf_list_appendstr(&b, "Hello"); + ecs_strbuf_list_appendstr(&b, "World"); + ecs_strbuf_list_pop(&b, "]"); + + ecs_strbuf_list_pop(&b, "}"); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "Foo{Foo 10,Bar[Hello,World],[Hello,World]}"); + ecs_os_free(str); +} + +void Strbuf_large_str() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_append(&b, "Foo"); + + int i; + for (i = 0; i < 200; i ++) { + ecs_strbuf_appendstr(&b, "Bar"); + } + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_assert(!strncmp(str, "Foo", 3)); + for (i = 1; i < 201; i ++) { + test_assert(!strncmp(&str[i * 3], "Bar", 3)); + } + + ecs_os_free(str); +} + +void Strbuf_empty_str() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + char *str = ecs_strbuf_get(&b); + test_assert(str == NULL); +} + +void Strbuf_append_zerocopy() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendstr(&b, "Foo"); + ecs_strbuf_appendstr_zerocpy(&b, ecs_os_strdup("Bar")); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "FooBar"); + ecs_os_free(str); +} + +void Strbuf_append_zerocopy_const() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendstr(&b, "Foo"); + ecs_strbuf_appendstr_zerocpy_const(&b, "Bar"); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "FooBar"); + ecs_os_free(str); +} + +void Strbuf_append_zerocopy_only() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendstr_zerocpy(&b, ecs_os_strdup("Bar")); + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "Bar"); + ecs_os_free(str); +} + +void Strbuf_reset() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendstr(&b, "Foo"); + ecs_strbuf_appendstr(&b, "Bar"); + ecs_strbuf_reset(&b); + char *str = ecs_strbuf_get(&b); + test_assert(str == NULL); +} + +void Strbuf_merge() { + ecs_strbuf_t b1 = ECS_STRBUF_INIT; + ecs_strbuf_appendstr(&b1, "Foo"); + ecs_strbuf_appendstr(&b1, "Bar"); + + ecs_strbuf_t b2 = ECS_STRBUF_INIT; + ecs_strbuf_appendstr(&b2, "Hello"); + ecs_strbuf_appendstr(&b2, "World"); + ecs_strbuf_mergebuff(&b1, &b2); + + char *str = ecs_strbuf_get(&b1); + test_str(str, "FooBarHelloWorld"); + ecs_os_free(str); + + str = ecs_strbuf_get(&b2); + test_assert(str == NULL); +} + +void Strbuf_app_buffer() { + char buf[256]; + ecs_strbuf_t b = ECS_STRBUF_INIT; + b.buf = buf; + b.max = 256; + ecs_strbuf_appendstr(&b, "Foo"); + ecs_strbuf_appendstr(&b, "Bar"); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "FooBar"); + ecs_os_free(str); +} diff --git a/fggl/ecs2/flecs/test/collections/src/Vector.c b/fggl/ecs2/flecs/test/collections/src/Vector.c new file mode 100644 index 0000000000000000000000000000000000000000..dba1c54efc60b578c4881ec7739e94d0e4aa2b9c --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/src/Vector.c @@ -0,0 +1,361 @@ +#include <collections.h> + +static +int compare_int( + const void *p1, + const void *p2) +{ + int v1 = *(int*)p1; + int v2 = *(int*)p2; + + if (v1 == v2) { + return 0; + } else if (v1 < v2) { + return -1; + } else { + return 1; + } +} + +ecs_vector_t* fill_array( + ecs_vector_t *array) +{ + int *elem; + int i; + for (i = 0; i < 4; i ++) { + elem = ecs_vector_add(&array, int); + *elem = i; + } + + return array; +} + +void Vector_setup() { + ecs_os_set_api_defaults(); +} + +void Vector_free_empty() { + ecs_vector_t *array = ecs_vector_new(int, 0); + test_assert(array != NULL); + ecs_vector_free(array); +} + +void Vector_count() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + test_int(ecs_vector_size(array), 4); + test_int(ecs_vector_count(array), 4); + ecs_vector_free(array); +} + +void Vector_count_empty() { + ecs_vector_t *array = ecs_vector_new(int, 0); + test_assert(array != NULL); + test_int(ecs_vector_count(array), 0); + ecs_vector_free(array); +} + +void Vector_get() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + int *elem = ecs_vector_get(array, int, 1); + test_assert(elem != NULL); + test_int(*elem, 1); + ecs_vector_free(array); +} + +void Vector_get_first() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + int *elem = ecs_vector_get(array, int, 0); + test_assert(elem != NULL); + test_int(*elem, 0); + ecs_vector_free(array); +} + +void Vector_get_last() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + int *elem = ecs_vector_get(array, int, 3); + test_assert(elem != NULL); + test_int(*elem, 3); + test_assert(elem == ecs_vector_last(array, int)); + ecs_vector_free(array); +} + +void Vector_get_last_from_empty() { + ecs_vector_t *array = ecs_vector_new(int, 4); + int *elem = ecs_vector_last(array, int); + test_assert(elem == NULL); + ecs_vector_free(array); +} + +void Vector_get_last_from_null() { + int *elem = ecs_vector_last(NULL, int); + test_assert(elem == NULL); +} + +void Vector_get_empty() { + ecs_vector_t *array = ecs_vector_new(int, 4); + int *elem = ecs_vector_get(array, int, 1); + test_assert(elem == NULL); + ecs_vector_free(array); +} + +void Vector_get_out_of_bound() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + int *elem = ecs_vector_get(array, int, 4); + test_assert(elem == NULL); + ecs_vector_free(array); +} + +void Vector_add_empty() { + ecs_vector_t *array = ecs_vector_new(int, 0); + test_int(ecs_vector_count(array), 0); + test_int(ecs_vector_size(array), 0); + + ecs_vector_add(&array, int); + test_int(ecs_vector_count(array), 1); + test_int(ecs_vector_size(array), 2); + ecs_vector_free(array); +} + +void Vector_add_resize() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + int *elem = ecs_vector_add(&array, int); + *elem = 4; + test_int(ecs_vector_size(array), 8); + test_int(ecs_vector_count(array), 5); + test_int(*(int*)ecs_vector_get(array, int, 0), 0); + test_int(*(int*)ecs_vector_get(array, int, 1), 1); + test_int(*(int*)ecs_vector_get(array, int, 2), 2); + test_int(*(int*)ecs_vector_get(array, int, 3), 3); + test_int(*(int*)ecs_vector_get(array, int, 4), 4); + test_assert(ecs_vector_get(array, int, 5) == NULL); + ecs_vector_free(array); +} + +void Vector_sort_rnd() { + int nums[] = {23, 16, 21, 13, 30, 5, 28, 31, 8, 19, 29, 12, 24, 14, 15, 1, 26, 18, 9, 25, 22, 0, 10, 3, 2, 17, 27, 20, 6, 11, 4, 7}; + ecs_vector_t *array = ecs_vector_new(int, 0); + + int32_t i, count = sizeof(nums) / sizeof(int); + for (i = 0; i < count; i ++) { + int *elem = ecs_vector_add(&array, int); + *elem = nums[i]; + } + + test_int(ecs_vector_count(array), sizeof(nums) / sizeof(int)); + + ecs_vector_sort(array, int, compare_int); + + int *buffer = ecs_vector_first(array, int); + count = ecs_vector_count(array); + + for (i = 0; i < count; i ++) { + test_int(buffer[i], i); + } + + ecs_vector_free(array); +} + +void Vector_sort_sorted() { + int nums[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + ecs_vector_t *array = ecs_vector_new(int, 0); + + int32_t i, count = sizeof(nums) / sizeof(int); + for (i = 0; i < count; i ++) { + int *elem = ecs_vector_add(&array, int); + *elem = nums[i]; + } + + test_int(ecs_vector_count(array), sizeof(nums) / sizeof(int)); + + ecs_vector_sort(array, int, compare_int); + + int *buffer = ecs_vector_first(array, int); + count = ecs_vector_count(array); + + for (i = 0; i < count; i ++) { + test_int(buffer[i], i); + } + + ecs_vector_free(array); +} + +void Vector_sort_empty() { + ecs_vector_t *array = ecs_vector_new(int, 0); + ecs_vector_sort(array, int, compare_int); + test_assert(true); // No observable side effects + ecs_vector_free(array); +} + +void Vector_sort_null() { + ecs_vector_sort(NULL, int, compare_int); + test_assert(true); // No observable side effects +} + +void Vector_size_of_null() { + test_int(ecs_vector_size(NULL), 0); +} + +void Vector_set_size_smaller_than_count() { + ecs_vector_t *vector = ecs_vector_new(int, 4); + vector = fill_array(vector); + test_int(ecs_vector_count(vector), 4); + test_int(ecs_vector_size(vector), 4); + + ecs_vector_set_size(&vector, int, 3); + + /* Vector contains 4 elements, will not be downsized */ + test_int(ecs_vector_size(vector), 4); + + ecs_vector_free(vector); +} + +void Vector_pop_elements() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + int value; + + test_assert( ecs_vector_pop(array, int, &value)); + test_int(value, 3); + + test_assert( ecs_vector_pop(array, int, &value)); + test_int(value, 2); + + test_assert( ecs_vector_pop(array, int, &value)); + test_int(value, 1); + + test_assert( ecs_vector_pop(array, int, &value)); + test_int(value, 0); + + test_assert( !ecs_vector_pop(array, int, &value)); + + ecs_vector_free(array); +} + +void Vector_pop_null() { + test_assert( !ecs_vector_pop(NULL, int, NULL)); +} + +void Vector_reclaim() { + ecs_vector_t *array = ecs_vector_new(int, 0); + array = fill_array(array); + array = fill_array(array); + array = fill_array(array); + + test_int(ecs_vector_count(array), 12); + test_int(ecs_vector_size(array), 16); + + ecs_vector_reclaim(&array, int); + + test_int(ecs_vector_count(array), 12); + test_int(ecs_vector_size(array), 12); + + ecs_vector_free(array); +} + +void Vector_grow() { + ecs_vector_t *array = ecs_vector_new(int, 0); + array = fill_array(array); + + test_int(ecs_vector_count(array), 4); + test_int(ecs_vector_size(array), 4); + + ecs_vector_grow(&array, int, 8); + + test_int(ecs_vector_count(array), 4); + test_int(ecs_vector_size(array), 16); + + ecs_vector_free(array); +} + +void Vector_copy() { + ecs_vector_t *array = ecs_vector_new(int, 4); + array = fill_array(array); + + ecs_vector_t *copy = ecs_vector_copy(array, int); + test_int(ecs_vector_count(copy), 4); + test_int(*(int*)ecs_vector_get(copy, int, 0), 0); + test_int(*(int*)ecs_vector_get(copy, int, 1), 1); + test_int(*(int*)ecs_vector_get(copy, int, 2), 2); + test_int(*(int*)ecs_vector_get(copy, int, 3), 3); + + ecs_vector_free(array); + ecs_vector_free(copy); +} + +void Vector_copy_null() { + test_assert( ecs_vector_copy(NULL, int) == NULL); +} + +void Vector_memory() { + ecs_size_t allocd = 0, used = 0; + ecs_vector_t *array = ecs_vector_new(int, 0); + array = fill_array(array); + + ecs_vector_memory(array, int, &allocd, &used); + test_int(allocd, 8 * sizeof(int)); + test_int(used, 4 * sizeof(int)); + + ecs_vector_free(array); +} + +void Vector_memory_from_null() { + ecs_size_t allocd = 0, used = 0; + ecs_vector_memory(NULL, int, &allocd, &used); + test_int(allocd, 0); + test_int(used, 0); +} + +void Vector_addn_to_null() { + ecs_vector_t *array = NULL; + void *ptr = ecs_vector_addn(&array, int, 4); + test_assert(array != NULL); + test_assert(ptr != NULL); + test_int(ecs_vector_count(array), 4); + ecs_vector_free(array); +} + +void Vector_addn_to_0_size() { + ecs_vector_t *array = ecs_vector_new(int, 0); + test_assert(array != NULL); + void *ptr = ecs_vector_addn(&array, int, 4); + test_assert(array != NULL); + test_assert(ptr != NULL); + test_int(ecs_vector_count(array), 4); + ecs_vector_free(array); +} + +void Vector_set_min_count() { + ecs_vector_t *array = ecs_vector_new(int, 0); + + ecs_vector_set_min_count(&array, int, 4); + test_int(ecs_vector_count(array), 4); + test_int(ecs_vector_size(array), 4); + + ecs_vector_free(array); +} + +void Vector_set_min_size() { + ecs_vector_t *array = ecs_vector_new(int, 0); + + ecs_vector_set_min_size(&array, int, 4); + test_int(ecs_vector_count(array), 0); + test_int(ecs_vector_size(array), 4); + + ecs_vector_free(array); +} + +void Vector_set_min_size_to_smaller() { + ecs_vector_t *array = ecs_vector_new(int, 4); + + ecs_vector_set_min_size(&array, int, 2); + test_int(ecs_vector_count(array), 0); + test_int(ecs_vector_size(array), 4); + + ecs_vector_free(array); +} diff --git a/fggl/ecs2/flecs/test/collections/src/main.c b/fggl/ecs2/flecs/test/collections/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..17423f8aa33a06d821a467309e8da2cd7494ae66 --- /dev/null +++ b/fggl/ecs2/flecs/test/collections/src/main.c @@ -0,0 +1,537 @@ + +/* A friendly warning from bake.test + * ---------------------------------------------------------------------------- + * This file is generated. To add/remove testcases modify the 'project.json' of + * the test project. ANY CHANGE TO THIS FILE IS LOST AFTER (RE)BUILDING! + * ---------------------------------------------------------------------------- + */ + +#include <collections.h> + +// Testsuite 'Vector' +void Vector_setup(void); +void Vector_free_empty(void); +void Vector_count(void); +void Vector_count_empty(void); +void Vector_get(void); +void Vector_get_first(void); +void Vector_get_last(void); +void Vector_get_last_from_empty(void); +void Vector_get_last_from_null(void); +void Vector_get_empty(void); +void Vector_get_out_of_bound(void); +void Vector_add_empty(void); +void Vector_add_resize(void); +void Vector_sort_rnd(void); +void Vector_sort_sorted(void); +void Vector_sort_empty(void); +void Vector_sort_null(void); +void Vector_size_of_null(void); +void Vector_set_size_smaller_than_count(void); +void Vector_pop_elements(void); +void Vector_pop_null(void); +void Vector_reclaim(void); +void Vector_grow(void); +void Vector_copy(void); +void Vector_copy_null(void); +void Vector_memory(void); +void Vector_memory_from_null(void); +void Vector_addn_to_null(void); +void Vector_addn_to_0_size(void); +void Vector_set_min_count(void); +void Vector_set_min_size(void); +void Vector_set_min_size_to_smaller(void); + +// Testsuite 'Queue' +void Queue_setup(void); +void Queue_free_empty(void); +void Queue_push(void); +void Queue_from_array(void); +void Queue_last(void); + +// Testsuite 'Map' +void Map_setup(void); +void Map_count(void); +void Map_count_empty(void); +void Map_set_overwrite(void); +void Map_set_rehash(void); +void Map_set_zero_buckets(void); +void Map_get(void); +void Map_get_all(void); +void Map_get_empty(void); +void Map_get_unknown(void); +void Map_iter(void); +void Map_iter_empty(void); +void Map_iter_zero_buckets(void); +void Map_iter_null(void); +void Map_remove(void); +void Map_remove_empty(void); +void Map_remove_unknown(void); +void Map_grow(void); +void Map_set_size_0(void); +void Map_ensure(void); + +// Testsuite 'Sparse' +void Sparse_setup(void); +void Sparse_add_1(void); +void Sparse_add_1_to_empty(void); +void Sparse_add_1_chunk_size_1(void); +void Sparse_add_n(void); +void Sparse_add_n_chunk_size_1(void); +void Sparse_remove(void); +void Sparse_remove_first(void); +void Sparse_remove_last(void); +void Sparse_remove_all(void); +void Sparse_remove_all_n_chunks(void); +void Sparse_clear_1(void); +void Sparse_clear_empty(void); +void Sparse_clear_n(void); +void Sparse_clear_n_chunks(void); +void Sparse_add_after_clear(void); +void Sparse_memory_null(void); +void Sparse_copy(void); +void Sparse_restore(void); +void Sparse_create_delete(void); +void Sparse_create_delete_2(void); +void Sparse_count_of_null(void); +void Sparse_size_of_null(void); +void Sparse_copy_null(void); + +// Testsuite 'Strbuf' +void Strbuf_setup(void); +void Strbuf_append(void); +void Strbuf_appendstr(void); +void Strbuf_appendstrn(void); +void Strbuf_appendstr_null(void); +void Strbuf_append_list(void); +void Strbuf_append_nested_list(void); +void Strbuf_large_str(void); +void Strbuf_empty_str(void); +void Strbuf_append_zerocopy(void); +void Strbuf_append_zerocopy_only(void); +void Strbuf_append_zerocopy_const(void); +void Strbuf_reset(void); +void Strbuf_merge(void); +void Strbuf_app_buffer(void); + +bake_test_case Vector_testcases[] = { + { + "free_empty", + Vector_free_empty + }, + { + "count", + Vector_count + }, + { + "count_empty", + Vector_count_empty + }, + { + "get", + Vector_get + }, + { + "get_first", + Vector_get_first + }, + { + "get_last", + Vector_get_last + }, + { + "get_last_from_empty", + Vector_get_last_from_empty + }, + { + "get_last_from_null", + Vector_get_last_from_null + }, + { + "get_empty", + Vector_get_empty + }, + { + "get_out_of_bound", + Vector_get_out_of_bound + }, + { + "add_empty", + Vector_add_empty + }, + { + "add_resize", + Vector_add_resize + }, + { + "sort_rnd", + Vector_sort_rnd + }, + { + "sort_sorted", + Vector_sort_sorted + }, + { + "sort_empty", + Vector_sort_empty + }, + { + "sort_null", + Vector_sort_null + }, + { + "size_of_null", + Vector_size_of_null + }, + { + "set_size_smaller_than_count", + Vector_set_size_smaller_than_count + }, + { + "pop_elements", + Vector_pop_elements + }, + { + "pop_null", + Vector_pop_null + }, + { + "reclaim", + Vector_reclaim + }, + { + "grow", + Vector_grow + }, + { + "copy", + Vector_copy + }, + { + "copy_null", + Vector_copy_null + }, + { + "memory", + Vector_memory + }, + { + "memory_from_null", + Vector_memory_from_null + }, + { + "addn_to_null", + Vector_addn_to_null + }, + { + "addn_to_0_size", + Vector_addn_to_0_size + }, + { + "set_min_count", + Vector_set_min_count + }, + { + "set_min_size", + Vector_set_min_size + }, + { + "set_min_size_to_smaller", + Vector_set_min_size_to_smaller + } +}; + +bake_test_case Queue_testcases[] = { + { + "free_empty", + Queue_free_empty + }, + { + "push", + Queue_push + }, + { + "from_array", + Queue_from_array + }, + { + "last", + Queue_last + } +}; + +bake_test_case Map_testcases[] = { + { + "count", + Map_count + }, + { + "count_empty", + Map_count_empty + }, + { + "set_overwrite", + Map_set_overwrite + }, + { + "set_rehash", + Map_set_rehash + }, + { + "set_zero_buckets", + Map_set_zero_buckets + }, + { + "get", + Map_get + }, + { + "get_all", + Map_get_all + }, + { + "get_empty", + Map_get_empty + }, + { + "get_unknown", + Map_get_unknown + }, + { + "iter", + Map_iter + }, + { + "iter_empty", + Map_iter_empty + }, + { + "iter_zero_buckets", + Map_iter_zero_buckets + }, + { + "iter_null", + Map_iter_null + }, + { + "remove", + Map_remove + }, + { + "remove_empty", + Map_remove_empty + }, + { + "remove_unknown", + Map_remove_unknown + }, + { + "grow", + Map_grow + }, + { + "set_size_0", + Map_set_size_0 + }, + { + "ensure", + Map_ensure + } +}; + +bake_test_case Sparse_testcases[] = { + { + "add_1", + Sparse_add_1 + }, + { + "add_1_to_empty", + Sparse_add_1_to_empty + }, + { + "add_1_chunk_size_1", + Sparse_add_1_chunk_size_1 + }, + { + "add_n", + Sparse_add_n + }, + { + "add_n_chunk_size_1", + Sparse_add_n_chunk_size_1 + }, + { + "remove", + Sparse_remove + }, + { + "remove_first", + Sparse_remove_first + }, + { + "remove_last", + Sparse_remove_last + }, + { + "remove_all", + Sparse_remove_all + }, + { + "remove_all_n_chunks", + Sparse_remove_all_n_chunks + }, + { + "clear_1", + Sparse_clear_1 + }, + { + "clear_empty", + Sparse_clear_empty + }, + { + "clear_n", + Sparse_clear_n + }, + { + "clear_n_chunks", + Sparse_clear_n_chunks + }, + { + "add_after_clear", + Sparse_add_after_clear + }, + { + "memory_null", + Sparse_memory_null + }, + { + "copy", + Sparse_copy + }, + { + "restore", + Sparse_restore + }, + { + "create_delete", + Sparse_create_delete + }, + { + "create_delete_2", + Sparse_create_delete_2 + }, + { + "count_of_null", + Sparse_count_of_null + }, + { + "size_of_null", + Sparse_size_of_null + }, + { + "copy_null", + Sparse_copy_null + } +}; + +bake_test_case Strbuf_testcases[] = { + { + "append", + Strbuf_append + }, + { + "appendstr", + Strbuf_appendstr + }, + { + "appendstrn", + Strbuf_appendstrn + }, + { + "appendstr_null", + Strbuf_appendstr_null + }, + { + "append_list", + Strbuf_append_list + }, + { + "append_nested_list", + Strbuf_append_nested_list + }, + { + "large_str", + Strbuf_large_str + }, + { + "empty_str", + Strbuf_empty_str + }, + { + "append_zerocopy", + Strbuf_append_zerocopy + }, + { + "append_zerocopy_only", + Strbuf_append_zerocopy_only + }, + { + "append_zerocopy_const", + Strbuf_append_zerocopy_const + }, + { + "reset", + Strbuf_reset + }, + { + "merge", + Strbuf_merge + }, + { + "app_buffer", + Strbuf_app_buffer + } +}; + +static bake_test_suite suites[] = { + { + "Vector", + Vector_setup, + NULL, + 31, + Vector_testcases + }, + { + "Queue", + Queue_setup, + NULL, + 4, + Queue_testcases + }, + { + "Map", + Map_setup, + NULL, + 19, + Map_testcases + }, + { + "Sparse", + Sparse_setup, + NULL, + 23, + Sparse_testcases + }, + { + "Strbuf", + Strbuf_setup, + NULL, + 14, + Strbuf_testcases + } +}; + +int main(int argc, char *argv[]) { + ut_init(argv[0]); + return bake_test_run("collections", argc, argv, suites, 5); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/include/cpp_api.h b/fggl/ecs2/flecs/test/cpp_api/include/cpp_api.h new file mode 100644 index 0000000000000000000000000000000000000000..4930f365e2cbecb6a4c50f3a35fe836163fcfd6b --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/include/cpp_api.h @@ -0,0 +1,340 @@ +#ifndef CPP_API_H +#define CPP_API_H + +/* This generated file contains includes for project dependencies */ +#include <cpp_api/bake_config.h> + +struct Position { + float x; + float y; +}; + +struct Velocity { + float x; + float y; +}; + +struct Mass { + float value; +}; + +struct Rotation { + float value; +}; + +struct Tag { }; + +struct Self { + flecs::entity_view value; +}; + +class Pod { +public: + Pod() { + ctor_invoked ++; + value = 10; + } + + Pod(int v) { + ctor_invoked ++; + value = v; + } + + ~Pod() { + dtor_invoked ++; + } + + Pod(const Pod& obj) { + copy_ctor_invoked ++; + *this = obj; + } + + Pod(Pod&& obj) { + move_ctor_invoked ++; + *this = std::move(obj); + } + + Pod& operator=(const Pod& obj) { + copy_invoked ++; + this->value = obj.value; + return *this; + } + + Pod& operator=(Pod&& obj) { + move_invoked ++; + this->value = obj.value; + return *this; + } + + int value; + + static int ctor_invoked; + static int dtor_invoked; + static int copy_invoked; + static int move_invoked; + static int copy_ctor_invoked; + static int move_ctor_invoked; +}; + +struct TagA { }; +struct TagB { }; +struct TagC { }; +struct TagD { }; +struct TagE { }; +struct TagF { }; +struct TagG { }; +struct TagH { }; +struct TagI { }; +struct TagJ { }; +struct TagK { }; +struct TagL { }; +struct TagM { }; +struct TagN { }; +struct TagO { }; +struct TagP { }; +struct TagQ { }; +struct TagR { }; +struct TagS { }; +struct TagT { }; + +template <typename T> +struct Template { + T x; + T y; +}; + +struct NoDefaultCtor { + NoDefaultCtor(int x) : x_(x) { } + NoDefaultCtor(const NoDefaultCtor& obj) = default; + NoDefaultCtor(NoDefaultCtor&& obj) = default; + + NoDefaultCtor& operator=(const NoDefaultCtor& obj) = default; + NoDefaultCtor& operator=(NoDefaultCtor&& obj) = default; + + ~NoDefaultCtor() { } + + int x_; +}; + +struct DefaultInit { + DefaultInit() : x_(99) { test_assert(y_ == 99); } + DefaultInit(int x) : x_(x) { test_assert(y_ == 99); } + DefaultInit(const DefaultInit& obj) = default; + DefaultInit(DefaultInit&& obj) = default; + + DefaultInit& operator=(const DefaultInit& obj) = default; + DefaultInit& operator=(DefaultInit&& obj) = default; + + ~DefaultInit() { } + + int x_; + int y_ = 99; +}; + +struct NoCopy { + NoCopy() : x_(99) { } + NoCopy(int x) : x_(x) { } + NoCopy(const NoCopy& obj) = delete; + NoCopy(NoCopy&& obj) = default; + + NoCopy& operator=(const NoCopy& obj) = delete; + NoCopy& operator=(NoCopy&& obj) = default; + + ~NoCopy() { } + + int x_; +}; + +struct NoMove { + NoMove() : x_(99) { } + NoMove(int x) : x_(x) { } + NoMove(const NoMove& obj) = default; + NoMove(NoMove&& obj) = delete; + + NoMove& operator=(const NoMove& obj) = default; + NoMove& operator=(NoMove&& obj) = delete; + + ~NoMove() { } + + int x_; +}; + +struct NoCopyCtor { + NoCopyCtor() : x_(99) { } + NoCopyCtor(int x) : x_(x) { } + NoCopyCtor(const NoCopyCtor& obj) = delete; + NoCopyCtor(NoCopyCtor&& obj) = default; + + NoCopyCtor& operator=(const NoCopyCtor& obj) = default; + NoCopyCtor& operator=(NoCopyCtor&& obj) = default; + + ~NoCopyCtor() { } + + int x_; +}; + +struct NoCopyAssign { + NoCopyAssign() : x_(99) { } + NoCopyAssign(int x) : x_(x) { } + NoCopyAssign(const NoCopyAssign& obj) = default; + NoCopyAssign(NoCopyAssign&& obj) = default; + + NoCopyAssign& operator=(const NoCopyAssign& obj) = delete; + NoCopyAssign& operator=(NoCopyAssign&& obj) = default; + + ~NoCopyAssign() { } + + int x_; +}; + +struct NoMoveCtor { + NoMoveCtor() : x_(99) { } + NoMoveCtor(int x) : x_(x) { } + NoMoveCtor(const NoMoveCtor& obj) = default; + NoMoveCtor(NoMoveCtor&& obj) = delete; + + NoMoveCtor& operator=(const NoMoveCtor& obj) = default; + NoMoveCtor& operator=(NoMoveCtor&& obj) = default; + + ~NoMoveCtor() { } + + int x_; +}; + +struct NoMoveAssign { + NoMoveAssign() : x_(99) { } + NoMoveAssign(int x) : x_(x) { } + NoMoveAssign(const NoMoveAssign& obj) = default; + NoMoveAssign(NoMoveAssign&& obj) = default; + + NoMoveAssign& operator=(const NoMoveAssign& obj) = default; + NoMoveAssign& operator=(NoMoveAssign&& obj) = delete; + + ~NoMoveAssign() { } + + int x_; +}; + +struct NoDtor { + NoDtor() : x_(99) { } + NoDtor(int x) : x_(x) { } + NoDtor(const NoDtor& obj) = default; + NoDtor(NoDtor&& obj) = default; + + NoDtor& operator=(const NoDtor& obj) = delete; + NoDtor& operator=(NoDtor&& obj) = default; + + ~NoDtor() = delete; + + int x_; +}; + +struct FlecsCtor { + FlecsCtor(flecs::world& w, flecs::entity e) : x_(89), e_(e) { } + + FlecsCtor(const FlecsCtor& obj) = delete; + FlecsCtor(FlecsCtor&& obj) = default; + + FlecsCtor& operator=(const FlecsCtor& obj) = default; + FlecsCtor& operator=(FlecsCtor&& obj) = default; + + ~FlecsCtor() { } + + int x_; + flecs::entity e_; +}; + +struct FlecsCtorDefaultCtor { + FlecsCtorDefaultCtor() : x_(99) { } + FlecsCtorDefaultCtor(flecs::world& w, flecs::entity e) : x_(89), e_(e) { } + + FlecsCtorDefaultCtor(const FlecsCtorDefaultCtor& obj) = delete; + FlecsCtorDefaultCtor(FlecsCtorDefaultCtor&& obj) = default; + + FlecsCtorDefaultCtor& operator=(const FlecsCtorDefaultCtor& obj) = default; + FlecsCtorDefaultCtor& operator=(FlecsCtorDefaultCtor&& obj) = default; + + ~FlecsCtorDefaultCtor() { } + + int x_; + flecs::entity e_; +}; + +struct DefaultCtorValueCtor { + DefaultCtorValueCtor() : x_(99) { } + DefaultCtorValueCtor(int x) : x_(x) { } + + DefaultCtorValueCtor(const DefaultCtorValueCtor& obj) = delete; + DefaultCtorValueCtor(DefaultCtorValueCtor&& obj) = default; + + DefaultCtorValueCtor& operator=(const DefaultCtorValueCtor& obj) = default; + DefaultCtorValueCtor& operator=(DefaultCtorValueCtor&& obj) = default; + + ~DefaultCtorValueCtor() { } + + int x_; +}; + +struct FlecsCtorValueCtor { + FlecsCtorValueCtor(int x) : x_(x) { } + FlecsCtorValueCtor(flecs::world& w, flecs::entity e) : x_(89), e_(e) { } + + FlecsCtorValueCtor(const FlecsCtorValueCtor& obj) = delete; + FlecsCtorValueCtor(FlecsCtorValueCtor&& obj) = default; + + FlecsCtorValueCtor& operator=(const FlecsCtorValueCtor& obj) = default; + FlecsCtorValueCtor& operator=(FlecsCtorValueCtor&& obj) = default; + + ~FlecsCtorValueCtor() { } + + int x_; + flecs::entity e_; +}; + +class CountNoDefaultCtor { +public: + CountNoDefaultCtor(int v) { + ctor_invoked ++; + value = v; + } + + ~CountNoDefaultCtor() { + dtor_invoked ++; + } + + CountNoDefaultCtor(const CountNoDefaultCtor& obj) { + copy_ctor_invoked ++; + this->value = obj.value; + } + + CountNoDefaultCtor(CountNoDefaultCtor&& obj) { + move_ctor_invoked ++; + this->value = obj.value; + } + + CountNoDefaultCtor& operator=(const CountNoDefaultCtor& obj) { + copy_invoked ++; + this->value = obj.value; + return *this; + } + + CountNoDefaultCtor& operator=(CountNoDefaultCtor&& obj) { + move_invoked ++; + this->value = obj.value; + return *this; + } + + int value; + + static int ctor_invoked; + static int dtor_invoked; + static int copy_invoked; + static int move_invoked; + static int copy_ctor_invoked; + static int move_ctor_invoked; +}; + +void install_test_abort(); + +#endif + diff --git a/fggl/ecs2/flecs/test/cpp_api/include/cpp_api/bake_config.h b/fggl/ecs2/flecs/test/cpp_api/include/cpp_api/bake_config.h new file mode 100644 index 0000000000000000000000000000000000000000..98b5da71ea8f80f1ef5f5a030accee361b7150b6 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/include/cpp_api/bake_config.h @@ -0,0 +1,29 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * 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 CPP_API_BAKE_CONFIG_H +#define CPP_API_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include <flecs.h> +#include <flecs_os_api_bake.h> +#ifdef __BAKE__ +#include <bake_util.h> +#endif +#include <bake_test.h> + +#endif + diff --git a/fggl/ecs2/flecs/test/cpp_api/project.json b/fggl/ecs2/flecs/test/cpp_api/project.json new file mode 100644 index 0000000000000000000000000000000000000000..056fdcde0b1053893b951fab789032c04b7d848f --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/project.json @@ -0,0 +1,755 @@ +{ + "id": "cpp_api", + "type": "application", + "value": { + "author": "Sander Mertens", + "description": "Test project for flecs", + "public": false, + "coverage": false, + "language": "c++", + "use": [ + "flecs", + "flecs.os_api.bake" + ] + }, + "test": { + "testsuites": [{ + "id": "Entity", + "testcases": [ + "new", + "new_named", + "new_named_from_scope", + "new_nested_named_from_scope", + "new_nested_named_from_nested_scope", + "new_add", + "new_add_2", + "new_set", + "new_set_2", + "add", + "add_2", + "add_entity", + "add_childof", + "add_instanceof", + "remove", + "remove_2", + "remove_entity", + "remove_childof", + "remove_instanceof", + "set", + "set_2", + "emplace", + "emplace_2", + "emplace_after_add", + "emplace_after_add_pair", + "emplace_w_self_ctor", + "replace", + "get_generic", + "get_mut_generic", + "get_generic_w_id", + "get_mut_generic_w_id", + "add_role", + "remove_role", + "has_role", + "pair_role", + "equals", + "compare_0", + "compare_id_t", + "compare_id", + "compare_literal", + "greater_than", + "less_than", + "not_0_or_1", + "not_true_or_false", + "has_childof", + "has_instanceof", + "has_instanceof_indirect", + "null_string", + "set_name", + "change_name", + "delete", + "clear", + "foce_owned", + "force_owned_2", + "force_owned_nested", + "force_owned_type", + "force_owned_type_w_pair", + "tag_has_size_zero", + "get_null_name", + "get_parent", + "is_component_enabled", + "is_enabled_component_enabled", + "is_disabled_component_enabled", + "get_type", + "get_nonempty_type", + "set_no_copy", + "set_copy", + "set_deduced", + "add_owned", + "set_owned", + "implicit_name_to_char", + "implicit_path_to_char", + "implicit_type_str_to_char", + "entity_to_entity_view", + "entity_view_to_entity_world", + "entity_view_to_entity_stage", + "create_entity_view_from_stage", + "set_template", + "get_1_component_w_callback", + "get_2_components_w_callback", + "set_1_component_w_callback", + "set_2_components_w_callback", + "set_3_components_w_callback", + "defer_set_1_component", + "defer_set_2_components", + "defer_set_3_components", + "set_2_w_on_set", + "defer_set_2_w_on_set", + "set_2_after_fluent", + "set_2_before_fluent", + "set_2_after_set_1", + "set_2_after_set_2", + "with_self", + "with_relation_type_self", + "with_relation_self", + "with_self_w_name", + "with_self_nested", + "with_after_builder_method", + "with_before_builder_method", + "with_scope", + "with_scope_nested", + "with_scope_nested_same_name_as_parent", + "scope_after_builder_method", + "scope_before_builder_method", + "no_recursive_lookup", + "defer_new_w_name", + "defer_new_w_nested_name", + "defer_new_w_scope_name", + "defer_new_w_scope_nested_name", + "defer_new_w_deferred_scope_nested_name", + "defer_new_w_scope", + "defer_new_w_with", + "defer_new_w_name_scope_with", + "defer_w_with_implicit_component", + "entity_id_str", + "pair_id_str", + "role_id_str", + "id_str_from_entity_view", + "id_str_from_entity", + "null_entity", + "null_entity_w_world", + "null_entity_w_0", + "null_entity_w_world_w_0", + "entity_view_null_entity", + "entity_view_null_entity_w_world", + "entity_view_null_entity_w_0", + "entity_view_null_entity_w_world_w_0", + "is_wildcard", + "has_id_t", + "has_pair_id_t", + "has_pair_id_t_w_type", + "has_id", + "has_pair_id", + "has_pair_id_w_type", + "has_wildcard_id", + "has_wildcard_pair_id", + "owns_id_t", + "owns_pair_id_t", + "owns_pair_id_t_w_type", + "owns_id", + "owns_pair_id", + "owns_pair_id_w_type", + "owns_wildcard_id", + "owns_wildcard_pair", + "id_from_world", + "id_pair_from_world", + "id_default_from_world", + "is_a", + "is_a_w_type", + "child_of", + "child_of_w_type", + "id_get_entity", + "id_get_invalid_entity" + ] + }, { + "id": "Pairs", + "testcases": [ + "add_component_pair", + "add_tag_pair", + "add_tag_pair_to_tag", + "remove_component_pair", + "remove_tag_pair", + "remove_tag_pair_to_tag", + "set_component_pair", + "set_tag_pair", + "system_1_pair_instance", + "system_2_pair_instances", + "override_pair", + "override_tag_pair", + "get_mut_pair", + "get_mut_pair_existing", + "get_mut_pair_tag", + "get_mut_pair_tag_existing", + "type_w_pair", + "type_w_pair_tag", + "type_w_pair_tags", + "type_w_tag_pair", + "override_pair_w_type", + "override_pair_w_type_tag", + "override_tag_pair_w_type", + "get_relation_from_id", + "get_w_object_from_id", + "get_recycled_relation_from_id", + "get_recycled_object_from_id", + "each", + "each_pair", + "each_pair_by_type", + "each_pair_w_childof", + "each_pair_w_recycled_rel", + "each_pair_w_recycled_obj", + "match_pair", + "match_pair_obj_wildcard", + "match_pair_rel_wildcard", + "match_pair_both_wildcard", + "has_tag_w_object", + "has_w_object_tag", + "add_pair_type", + "remove_pair_type", + "set_pair_type", + "has_pair_type", + "get_1_pair_arg", + "get_2_pair_arg", + "set_1_pair_arg", + "set_2_pair_arg", + "get_inline_pair_type", + "set_inline_pair_type", + "get_pair_type_object", + "set_pair_type_object", + "set_get_w_object_variants" + ] + }, { + "id": "Switch", + "testcases": [ + "add_case", + "get_case", + "system_w_case", + "system_w_switch", + "add_case_w_type", + "add_switch_w_type", + "add_switch_w_type_component_first", + "add_remove_switch_w_type" + ] + }, { + "id": "Paths", + "testcases": [ + "name", + "path_depth_1", + "path_depth_2", + "entity_lookup_name", + "entity_lookup_depth_1", + "entity_lookup_depth_2", + "alias_component", + "alias_scoped_component", + "alias_scoped_component_w_name", + "alias_entity", + "alias_entity_by_name", + "alias_entity_by_scoped_name" + ] + }, { + "id": "Type", + "testcases": [ + "add_2", + "add_instanceof", + "add_childof", + "1_component", + "2_component", + "1_component_signature", + "2_component_signature", + "type_no_name", + "null_args", + "has_type", + "has_entity", + "has_pair_type", + "has_pair_entity", + "get", + "get_out_of_range" + ] + }, { + "id": "System", + "testcases": [ + "action", + "action_const", + "action_shared", + "action_optional", + "each", + "each_const", + "each_shared", + "each_optional", + "signature", + "signature_const", + "signature_shared", + "signature_optional", + "copy_name_on_create", + "nested_system", + "empty_signature", + "action_tag", + "iter_tag", + "each_tag", + "system_from_id", + "set_interval", + "order_by_type", + "order_by_id", + "order_by_type_after_create", + "order_by_id_after_create", + "get_query", + "add_from_each", + "delete_from_each", + "add_from_each_world_handle", + "new_from_each", + "add_from_iter", + "delete_from_iter", + "add_from_iter_world_handle", + "new_from_iter", + "each_w_mut_children_it", + "readonly_children_iter", + "rate_filter", + "update_rate_filter", + "default_ctor", + "test_auto_defer_each", + "test_auto_defer_iter", + "custom_pipeline", + "system_w_self" + ] + }, { + "id": "Trigger", + "testcases": [ + "on_add", + "on_remove", + "on_add_tag_action", + "on_add_tag_iter", + "on_add_tag_each", + "trigger_w_self" + ] + }, { + "id": "Query", + "testcases": [ + "action", + "action_const", + "action_shared", + "action_optional", + "each", + "each_const", + "each_shared", + "each_optional", + "signature", + "signature_const", + "signature_shared", + "signature_optional", + "subquery", + "subquery_w_expr", + "query_single_pair", + "tag_w_each", + "shared_tag_w_each", + "sort_by", + "changed", + "orphaned", + "default_ctor", + "expr_w_template", + "query_type_w_template", + "compare_term_id", + "test_no_defer_each", + "test_no_defer_iter", + "inspect_terms", + "inspect_terms_w_each", + "comp_to_str", + "pair_to_str", + "oper_not_to_str", + "oper_optional_to_str", + "oper_or_to_str", + "each_pair_type", + "iter_pair_type", + "term_pair_type", + "each_no_entity_1_comp", + "each_no_entity_2_comps", + "iter_no_comps_1_comp", + "iter_no_comps_2_comps", + "iter_no_comps_no_comps", + "each_pair_object", + "iter_pair_object", + "iter_query_in_system", + "iter_type" + ] + }, { + "id": "QueryBuilder", + "testcases": [ + "builder_assign_same_type", + "builder_assign_from_empty", + "builder_assign_to_empty", + "builder_build", + "builder_build_to_auto", + "builder_build_n_statements", + "1_type", + "add_1_type", + "add_2_types", + "add_1_type_w_1_type", + "add_2_types_w_1_type", + "add_pair", + "add_not", + "add_or", + "add_optional", + "ptr_type", + "const_type", + "string_term", + "singleton_term", + "isa_superset_term", + "isa_self_superset_term", + "childof_superset_term", + "childof_self_superset_term", + "isa_superset_term_w_each", + "isa_self_superset_term_w_each", + "childof_superset_term_w_each", + "childof_self_superset_term_w_each", + "isa_superset_shortcut", + "isa_superset_shortcut_w_self", + "childof_superset_shortcut", + "childof_superset_shortcut_w_self", + "isa_superset_max_depth_1", + "isa_superset_max_depth_2", + "isa_superset_min_depth_2", + "isa_superset_min_depth_2_max_depth_3", + "role", + "relation", + "relation_w_object_wildcard", + "relation_w_predicate_wildcard", + "add_pair_w_rel_type", + "template_term", + "explicit_subject_w_id", + "explicit_subject_w_type", + "explicit_object_w_id", + "explicit_object_w_type", + "explicit_term", + "explicit_term_w_type", + "explicit_term_w_pair_type", + "explicit_term_w_id", + "explicit_term_w_pair_id", + "1_term_to_empty", + "2_subsequent_args", + "optional_tag_is_set", + "10_terms", + "20_terms" + ] + }, { + "id": "FilterBuilder", + "testcases": [ + "builder_assign_same_type", + "builder_assign_from_empty", + "builder_assign_to_empty", + "builder_build", + "builder_build_to_auto", + "builder_build_n_statements", + "1_type", + "add_1_type", + "add_2_types", + "add_1_type_w_1_type", + "add_2_types_w_1_type", + "add_pair", + "add_not", + "add_or", + "add_optional", + "ptr_type", + "const_type", + "string_term", + "singleton_term", + "isa_superset_term", + "isa_self_superset_term", + "childof_superset_term", + "childof_self_superset_term", + "isa_superset_term_w_each", + "isa_self_superset_term_w_each", + "childof_superset_term_w_each", + "childof_self_superset_term_w_each", + "isa_superset_shortcut", + "isa_superset_shortcut_w_self", + "childof_superset_shortcut", + "childof_superset_shortcut_w_self", + "isa_superset_max_depth_1", + "isa_superset_max_depth_2", + "isa_superset_min_depth_2", + "isa_superset_min_depth_2_max_depth_3", + "relation", + "relation_w_object_wildcard", + "relation_w_predicate_wildcard", + "add_pair_w_rel_type", + "template_term", + "explicit_subject_w_id", + "explicit_subject_w_type", + "explicit_object_w_id", + "explicit_object_w_type", + "explicit_term", + "explicit_term_w_type", + "explicit_term_w_pair_type", + "explicit_term_w_id", + "explicit_term_w_pair_id", + "1_term_to_empty", + "2_subsequent_args", + "filter_as_arg", + "filter_as_move_arg", + "filter_as_return", + "filter_copy", + "world_each_filter_1_component", + "world_each_filter_2_components", + "world_each_filter_1_component_no_entity", + "world_each_filter_2_components_no_entity", + "10_terms", + "20_terms", + "term_after_arg" + ] + }, { + "id": "SystemBuilder", + "testcases": [ + "builder_assign_same_type", + "builder_assign_from_empty", + "builder_build_to_auto", + "builder_build_n_statements", + "1_type", + "add_1_type", + "add_2_types", + "add_1_type_w_1_type", + "add_2_types_w_1_type", + "add_pair", + "add_not", + "add_or", + "add_optional", + "ptr_type", + "const_type", + "string_term", + "singleton_term", + "10_terms", + "20_terms" + ] + }, { + "id": "Observer", + "testcases": [ + "2_terms_on_add", + "2_terms_on_remove", + "2_terms_on_set", + "2_terms_un_set", + "observer_w_self", + "10_terms", + "20_terms" + ] + }, { + "id": "Filter", + "testcases": [ + "term_each_component", + "term_each_tag", + "term_each_id", + "term_each_pair_type", + "term_each_pair_id", + "term_each_pair_relation_wildcard", + "term_each_pair_object_wildcard" + ] + }, { + "id": "ComponentLifecycle", + "testcases": [ + "ctor_on_add", + "dtor_on_remove", + "move_on_add", + "move_on_remove", + "copy_on_set", + "copy_on_override", + "non_pod_add", + "non_pod_remove", + "non_pod_set", + "non_pod_override", + "get_mut_new", + "get_mut_existing", + "pod_component", + "relocatable_component", + "implicit_component", + "implicit_after_query", + "deleted_copy", + "no_default_ctor_emplace", + "default_init", + "no_default_ctor_add", + "no_default_ctor_add_relation", + "no_default_ctor_add_w_object", + "no_default_ctor_set", + "no_copy_ctor", + "no_move_ctor", + "no_copy_assign", + "no_move_assign", + "no_copy", + "no_move", + "no_dtor", + "flecs_ctor", + "flecs_ctor_w_default_ctor", + "default_ctor_w_value_ctor", + "flecs_ctor_w_value_ctor", + "no_default_ctor_move_ctor_on_set", + "emplace_w_ctor", + "emplace_no_default_ctor", + "emplace_existing", + "emplace_singleton", + "dtor_w_non_trivial_implicit_move", + "dtor_w_non_trivial_explicit_move" + ] + }, { + "id": "Refs", + "testcases": [ + "get_ref", + "ref_after_add", + "ref_after_remove", + "ref_after_set", + "ref_before_set" + ] + }, { + "id": "Module", + "testcases": [ + "import", + "lookup_from_scope", + "nested_module", + "nested_type_module", + "module_type_w_explicit_name", + "component_redefinition_outside_module", + "module_tag_on_namespace", + "dtor_on_fini" + ] + }, { + "id": "ImplicitComponents", + "testcases": [ + "add", + "remove", + "has", + "set", + "get", + "add_pair", + "remove_pair", + "module", + "system", + "system_optional", + "system_const", + "query", + "implicit_name", + "reinit", + "reinit_scoped", + "reinit_w_lifecycle", + "first_use_in_system", + "first_use_tag_in_system", + "use_const", + "use_const_w_stage", + "use_const_w_threads", + "implicit_base", + "implicit_const", + "implicit_ref", + "implicit_ptr", + "implicit_const_ref" + ] + }, { + "id": "Snapshot", + "testcases": [ + "simple_snapshot", + "snapshot_iter" + ] + }, { + "id": "WorldFactory", + "testcases": [ + "entity", + "entity_w_name", + "entity_w_id", + "prefab", + "prefab_w_name", + "type", + "type_w_name", + "system", + "system_w_name", + "system_w_expr", + "query", + "query_w_expr", + "snapshot", + "module", + "module_w_name" + ] + }, { + "id": "World", + "testcases": [ + "builtin_components", + "multi_world_empty", + "multi_world_component", + "multi_world_component_namespace", + "multi_world_module", + "type_id", + "different_comp_same_name", + "reregister_after_reset", + "reregister_after_reset_w_namespace", + "reregister_namespace", + "implicit_reregister_after_reset", + "reregister_after_reset_different_name", + "reimport", + "reimport_module_after_reset", + "reimport_module_new_world", + "reimport_namespaced_module", + "c_interop_module", + "c_interop_after_reset", + "implicit_register_w_new_world", + "count", + "staged_count", + "async_stage_add", + "with_tag", + "with_tag_type", + "with_relation", + "with_relation_type", + "with_relation_object_type", + "with_tag_nested", + "with_scope", + "with_scope_nested", + "recursive_lookup", + "type_w_tag_name", + "entity_w_tag_name", + "template_component_name", + "template_component_w_namespace_name", + "template_component_w_same_namespace_name", + "template_component_w_namespace_name_and_namespaced_arg", + "template_component_w_same_namespace_name_and_namespaced_arg", + "entity_as_tag", + "entity_w_name_as_tag", + "type_as_tag", + "entity_as_component", + "entity_w_name_as_component", + "entity_as_component_2_worlds", + "entity_as_namespaced_component_2_worlds", + "entity_as_component_2_worlds_implicit_namespaced", + "type_as_component", + "type_w_name_as_component", + "component_as_component" + ] + }, { + "id": "Singleton", + "testcases": [ + "set_get_singleton", + "get_mut_singleton", + "emplace_singleton", + "modified_singleton", + "patch_singleton", + "add_singleton", + "remove_singleton", + "has_singleton", + "singleton_system", + "get_singleton", + "type_id_from_world" + ] + }, { + "id": "Misc", + "setup": true, + "testcases": [ + "string_compare_w_char_ptr", + "string_compare_w_char_ptr_length_diff", + "string_compare_w_string", + "string_view_compare_w_char_ptr", + "string_view_compare_w_string", + "string_compare_nullptr", + "nullptr_string_compare", + "nullptr_string_compare_nullptr" + ] + }] + } +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/ComponentLifecycle.cpp b/fggl/ecs2/flecs/test/cpp_api/src/ComponentLifecycle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c9619247365241a3773ec7958dedf0747494731d --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/ComponentLifecycle.cpp @@ -0,0 +1,904 @@ +// Catch static asserts +#define flecs_static_assert(cond, str)\ + ecs_assert(cond, ECS_INVALID_OPERATION, str) + +#include <cpp_api.h> + +#include <string> + +int Pod::ctor_invoked = 0; +int Pod::dtor_invoked = 0; +int Pod::copy_invoked = 0; +int Pod::move_invoked = 0; +int Pod::copy_ctor_invoked = 0; +int Pod::move_ctor_invoked = 0; + +int CountNoDefaultCtor::ctor_invoked = 0; +int CountNoDefaultCtor::dtor_invoked = 0; +int CountNoDefaultCtor::copy_invoked = 0; +int CountNoDefaultCtor::move_invoked = 0; +int CountNoDefaultCtor::copy_ctor_invoked = 0; +int CountNoDefaultCtor::move_ctor_invoked = 0; + +class Str { +public: + std::string value; +}; + +void ComponentLifecycle_ctor_on_add() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); + + test_int(pod->value, 10); +} + +void ComponentLifecycle_dtor_on_remove() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + test_int(Pod::ctor_invoked, 1); + + e.remove<Pod>(); + test_assert(!e.has<Pod>()); + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 1); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); +} + +void ComponentLifecycle_move_on_add() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); + test_int(Pod::copy_ctor_invoked, 0); + test_int(Pod::move_ctor_invoked, 0); + + e.add<Position>(); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 1); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 1); + test_int(Pod::copy_ctor_invoked, 0); + test_int(Pod::move_ctor_invoked, 1); + + test_int(pod->value, 10); +} + +void ComponentLifecycle_move_on_remove() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world).add<Position>().add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); + test_int(Pod::copy_ctor_invoked, 0); + test_int(Pod::move_ctor_invoked, 0); + + e.remove<Position>(); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 1); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 1); + test_int(Pod::copy_ctor_invoked, 0); + test_int(Pod::move_ctor_invoked, 1); + + test_int(pod->value, 10); +} + +void ComponentLifecycle_copy_on_set() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world); + test_assert(e.id() != 0); + + e.set<Pod>({20}); + test_assert(e.has<Pod>()); + test_int(Pod::ctor_invoked, 2); + test_int(Pod::dtor_invoked, 1); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 1); +} + +void ComponentLifecycle_copy_on_override() { + flecs::world world; + + world.component<Pod>(); + + auto base = flecs::entity(world); + test_assert(base.id() != 0); + + base.set<Pod>({10}); + test_int(Pod::ctor_invoked, 2); + test_int(Pod::dtor_invoked, 1); + test_int(Pod::copy_invoked, 0); + Pod::ctor_invoked = 0; + Pod::dtor_invoked = 0; + Pod::copy_invoked = 0; + Pod::move_invoked = 0; + + auto e = flecs::entity(world); + test_assert(e.id() != 0); + + e.add(flecs::IsA, base); + test_int(Pod::ctor_invoked, 0); + + e.add<Pod>(); + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 1); + test_int(Pod::move_invoked, 0); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + test_int(pod->value, 10); +} + +void ComponentLifecycle_non_pod_add() { + flecs::world world; + + flecs::component<Str>(world, "Str"); + + auto e = flecs::entity(world).add<Str>(); + test_assert(e.id() != 0); + test_assert(e.has<Str>()); + + const Str *str = e.get<Str>(); + test_assert(str != NULL); + test_assert(str->value == ""); +} + +void ComponentLifecycle_non_pod_remove() { + flecs::world world; + + flecs::component<Str>(world, "Str"); + + auto e = flecs::entity(world).add<Str>(); + test_assert(e.id() != 0); + test_assert(e.has<Str>()); + + e.remove<Str>(); + test_assert(!e.has<Str>()); +} + +void ComponentLifecycle_non_pod_set() { + flecs::world world; + + flecs::component<Str>(world, "Str"); + + auto e = flecs::entity(world) + .set<Str>({"Hello World"}); + test_assert(e.id() != 0); + test_assert(e.has<Str>()); + + const Str *str = e.get<Str>(); + test_assert(str != NULL); + test_assert(str->value == "Hello World"); +} + +void ComponentLifecycle_non_pod_override() { + flecs::world world; + + flecs::component<Str>(world, "Str"); + + auto base = flecs::entity(world); + test_assert(base.id() != 0); + + base.set<Str>({"Hello World"}); + + auto e = flecs::entity(world); + test_assert(e.id() != 0); + + e.add(flecs::IsA, base); + + e.add<Str>(); + + const Str *str = e.get<Str>(); + test_assert(str != NULL); + test_assert(str->value == "Hello World"); +} + +void ComponentLifecycle_get_mut_new() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world); + test_assert(e.id() != 0); + + Pod* value = e.get_mut<Pod>(); + test_assert(value != NULL); + + Pod::ctor_invoked = 1; + Pod::dtor_invoked = 0; + Pod::copy_invoked = 0; + Pod::move_invoked = 0; + + e.modified<Pod>(); + + Pod::ctor_invoked = 1; + Pod::dtor_invoked = 0; + Pod::copy_invoked = 0; + Pod::move_invoked = 0; +} + +void ComponentLifecycle_get_mut_existing() { + flecs::world world; + + world.component<Pod>(); + + auto e = flecs::entity(world); + test_assert(e.id() != 0); + + Pod* value = e.get_mut<Pod>(); + test_assert(value != NULL); + + Pod::ctor_invoked = 1; + Pod::dtor_invoked = 0; + Pod::copy_invoked = 0; + Pod::move_invoked = 0; + + value = e.get_mut<Pod>(); + test_assert(value != NULL); + + /* Repeated calls to get_mut should not invoke constructor */ + Pod::ctor_invoked = 1; + Pod::dtor_invoked = 0; + Pod::copy_invoked = 0; + Pod::move_invoked = 0; +} + +void ComponentLifecycle_pod_component() { + flecs::world world; + + flecs::pod_component<Pod>(world, "Pod"); + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + + e.remove<Pod>(); + test_assert(!e.has<Pod>()); + + /* Component is registered as pod, no lifecycle actions should be invoked */ + test_int(Pod::ctor_invoked, 0); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); +} + +void ComponentLifecycle_relocatable_component() { + flecs::world world; + + flecs::relocatable_component<Pod>(world, "Pod"); + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + + test_int(pod->value, 10); + + /* Component is registered as relocatable, ctor/dtor/copy are registered, + * but move is not. */ + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); + + /* Add another entity, this moves the existing component, but should not + * invoke move assignment */ + flecs::entity(world).add<Pod>(); + test_int(Pod::ctor_invoked, 2); + test_int(Pod::move_invoked, 0); +} + +void ComponentLifecycle_implicit_component() { + flecs::world world; + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + test_int(Pod::ctor_invoked, 1); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + + test_int(pod->value, 10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); + + flecs::entity(world).add<Pod>(); + flecs::entity(world).add<Pod>(); + test_int(Pod::ctor_invoked, 5); + test_int(Pod::move_invoked, 2); +} + +void ComponentLifecycle_implicit_after_query() { + flecs::world world; + + world.query<Pod>(); + + auto e = flecs::entity(world).add<Pod>(); + test_assert(e.id() != 0); + test_assert(e.has<Pod>()); + test_int(Pod::ctor_invoked, 1); + + const Pod *pod = e.get<Pod>(); + test_assert(pod != NULL); + + test_int(pod->value, 10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + test_int(Pod::copy_invoked, 0); + test_int(Pod::move_invoked, 0); + + flecs::entity(world).add<Pod>(); + flecs::entity(world).add<Pod>(); + test_int(Pod::ctor_invoked, 5); + test_int(Pod::move_invoked, 2); +} + +template <typename T, typename std::enable_if< + !flecs::has_flecs_ctor<T>::value, void>::type* = nullptr> +static void try_add(flecs::world& ecs) { + flecs::entity e = ecs.entity().add<T>(); + + test_assert(e.has<T>()); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 99); + + e.remove<T>(); + test_assert(!e.has<T>()); +} + +template <typename T, typename std::enable_if< + flecs::has_flecs_ctor<T>::value, void>::type* = nullptr> +static void try_add(flecs::world& ecs) { + flecs::entity e = ecs.entity().add<T>(); + test_assert(e.has<T>()); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 89); + test_int(ptr->e_, e); + + e.remove<T>(); + test_assert(!e.has<T>()); +} + +template <typename T> +static void try_add_relation(flecs::world& ecs) { + flecs::entity obj = ecs.entity(); + + flecs::entity e = ecs.entity().add<T>(obj); + test_assert(e.has<T>()); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 89); + + e.remove<T>(); + test_assert(!e.has<T>()); +} + +template <typename T> +static void try_add_w_object(flecs::world& ecs) { + flecs::entity rel = ecs.entity(); + + flecs::entity e = ecs.entity().add_w_object<T>(rel); + test_assert(e.has<T>()); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 89); + + e.remove<T>(); + test_assert(!e.has<T>()); +} + +template <typename T, typename std::enable_if< + !flecs::has_flecs_ctor<T>::value, void>::type* = nullptr> +static void try_set(flecs::world& ecs) { + flecs::entity e = ecs.entity().set<T>({10}); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 10); +} + +template <typename T, typename std::enable_if< + flecs::has_flecs_ctor<T>::value, void>::type* = nullptr> +static void try_set(flecs::world& ecs) { + flecs::entity e = ecs.entity().set<T>({10}); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 10); + test_int(ptr->e_, 0); + + e.remove<T>(); +} + +template <typename T> +static void try_emplace(flecs::world& ecs) { + flecs::entity e = ecs.entity().emplace<T>(10); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 10); +} + +template <typename T, typename std::enable_if< + !flecs::has_flecs_ctor<T>::value, void>::type* = nullptr> +static void try_set_default(flecs::world& ecs) { + flecs::entity e = ecs.entity().set(T()); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 99); + + e.remove<T>(); +} + +template <typename T, typename std::enable_if< + flecs::has_flecs_ctor<T>::value, void>::type* = nullptr> +static void try_set_default(flecs::world& ecs) { + flecs::entity e = ecs.entity().set(T()); + + const T *ptr = e.get<T>(); + test_int(ptr->x_, 99); + test_int(ptr->e_, 0); + + e.remove<T>(); +} + +void ComponentLifecycle_deleted_copy() { + flecs::world ecs; + + ecs.component<NoCopy>(); + + try_add<NoCopy>(ecs); + + try_set<NoCopy>(ecs); +} + +void ComponentLifecycle_no_default_ctor_emplace() { + flecs::world ecs; + + ecs.component<NoDefaultCtor>(); + + try_emplace<NoDefaultCtor>(ecs); +} + +void ComponentLifecycle_default_init() { + flecs::world ecs; + + ecs.component<DefaultInit>(); + + try_add<DefaultInit>(ecs); + + try_set<DefaultInit>(ecs); +} + +void ComponentLifecycle_no_default_ctor_add() { + install_test_abort(); + + flecs::world ecs; + + ecs.component<NoDefaultCtor>(); + + test_expect_abort(); + + try_add<NoDefaultCtor>(ecs); +} + +void ComponentLifecycle_no_default_ctor_add_relation() { + install_test_abort(); + + flecs::world ecs; + + ecs.component<NoDefaultCtor>(); + + test_expect_abort(); + + try_add_relation<NoDefaultCtor>(ecs); +} + +void ComponentLifecycle_no_default_ctor_add_w_object() { + install_test_abort(); + + flecs::world ecs; + + ecs.component<NoDefaultCtor>(); + + test_expect_abort(); + + try_add_w_object<NoDefaultCtor>(ecs); +} + +void ComponentLifecycle_no_default_ctor_set() { + install_test_abort(); + + flecs::world ecs; + + ecs.component<NoDefaultCtor>(); + + test_expect_abort(); + + try_set<NoDefaultCtor>(ecs); +} + +void ComponentLifecycle_no_copy_ctor() { + flecs::world ecs; + + ecs.component<NoCopyCtor>(); + + try_add<NoCopyCtor>(ecs); + + try_set<NoCopyCtor>(ecs); +} + +void ComponentLifecycle_no_move_ctor() { + install_test_abort(); + + flecs::world ecs; + + test_expect_abort(); + + ecs.component<NoMoveCtor>(); +} + +void ComponentLifecycle_no_copy_assign() { + flecs::world ecs; + + ecs.component<NoCopyAssign>(); + + try_add<NoCopyAssign>(ecs); + + try_set<NoCopyAssign>(ecs); +} + +void ComponentLifecycle_no_move_assign() { + install_test_abort(); + + flecs::world ecs; + + test_expect_abort(); + + ecs.component<NoMoveAssign>(); +} + +void ComponentLifecycle_no_copy() { + flecs::world ecs; + + ecs.component<NoCopy>(); + + try_add<NoCopy>(ecs); + + try_set<NoCopy>(ecs); +} + +void ComponentLifecycle_no_move() { + install_test_abort(); + + flecs::world ecs; + + test_expect_abort(); + + ecs.component<NoMove>(); +} + +void ComponentLifecycle_no_dtor() { + install_test_abort(); + + flecs::world ecs; + + test_expect_abort(); + + ecs.component<NoDtor>(); +} + +void ComponentLifecycle_flecs_ctor() { + flecs::world ecs; + + ecs.component<FlecsCtor>(); + + try_add<FlecsCtor>(ecs); +} + +void ComponentLifecycle_flecs_ctor_w_default_ctor() { + flecs::world ecs; + + ecs.component<FlecsCtorDefaultCtor>(); + + try_add<FlecsCtorDefaultCtor>(ecs); + + try_set_default<FlecsCtorDefaultCtor>(ecs); +} + +void ComponentLifecycle_default_ctor_w_value_ctor() { + flecs::world ecs; + + ecs.component<DefaultCtorValueCtor>(); + + try_add<DefaultCtorValueCtor>(ecs); + + try_set<DefaultCtorValueCtor>(ecs); + + try_set_default<FlecsCtorDefaultCtor>(ecs); +} + +void ComponentLifecycle_flecs_ctor_w_value_ctor() { + flecs::world ecs; + + ecs.component<FlecsCtorValueCtor>(); + + try_add<FlecsCtorValueCtor>(ecs); + + try_set<FlecsCtorValueCtor>(ecs); +} + +void ComponentLifecycle_no_default_ctor_move_ctor_on_set() { + flecs::world ecs; + + ecs.component<CountNoDefaultCtor>(); + + // Emplace, construct + auto e = ecs.entity().emplace<CountNoDefaultCtor>(10); + test_assert(e.has<CountNoDefaultCtor>()); + + const CountNoDefaultCtor* ptr = e.get<CountNoDefaultCtor>(); + test_assert(ptr != NULL); + test_int(ptr->value, 10); + + test_int(CountNoDefaultCtor::ctor_invoked, 1); + test_int(CountNoDefaultCtor::dtor_invoked, 0); + test_int(CountNoDefaultCtor::copy_invoked, 0); + test_int(CountNoDefaultCtor::move_invoked, 0); + test_int(CountNoDefaultCtor::copy_ctor_invoked, 0); + test_int(CountNoDefaultCtor::move_ctor_invoked, 0); + + // Set, move assign + e.set<CountNoDefaultCtor>({10}); + + test_int(CountNoDefaultCtor::ctor_invoked, 2); + test_int(CountNoDefaultCtor::dtor_invoked, 1); + test_int(CountNoDefaultCtor::copy_invoked, 0); + test_int(CountNoDefaultCtor::move_invoked, 1); + test_int(CountNoDefaultCtor::copy_ctor_invoked, 0); + test_int(CountNoDefaultCtor::move_ctor_invoked, 0); +} + +void ComponentLifecycle_emplace_w_ctor() { + flecs::world ecs; + + auto e = ecs.entity() + .emplace<Pod>(10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + + const Pod *ptr = e.get<Pod>(); + test_assert(ptr != NULL); + test_int(ptr->value, 10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); +} + +void ComponentLifecycle_emplace_no_default_ctor() { + flecs::world ecs; + + auto e = ecs.entity() + .emplace<CountNoDefaultCtor>(10); + + test_int(CountNoDefaultCtor::ctor_invoked, 1); + test_int(CountNoDefaultCtor::dtor_invoked, 0); + + const CountNoDefaultCtor *ptr = e.get<CountNoDefaultCtor>(); + test_assert(ptr != NULL); + test_int(ptr->value, 10); + + test_int(CountNoDefaultCtor::ctor_invoked, 1); + test_int(CountNoDefaultCtor::dtor_invoked, 0); +} + +void ComponentLifecycle_emplace_existing() { + install_test_abort(); + + flecs::world ecs; + + auto e = ecs.entity() + .emplace<Pod>(10); + + const Pod *ptr = e.get<Pod>(); + test_assert(ptr != NULL); + test_int(ptr->value, 10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + + test_expect_abort(); + e.emplace<Pod>(20); +} + +void ComponentLifecycle_emplace_singleton() { + flecs::world ecs; + + ecs.emplace<Pod>(10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); + + const Pod *ptr = ecs.get<Pod>(); + test_assert(ptr != NULL); + test_int(ptr->value, 10); + + test_int(Pod::ctor_invoked, 1); + test_int(Pod::dtor_invoked, 0); +} + +class CtorDtorNonTrivial { +public: + CtorDtorNonTrivial(int x) : x_(x) { + ctor_invoked ++; + } + + ~CtorDtorNonTrivial() { + dtor_invoked ++; + dtor_value = x_; + } + + int x_; + std::string str_; + + static int ctor_invoked; + static int dtor_invoked; + static int dtor_value; +}; + +int CtorDtorNonTrivial::ctor_invoked; +int CtorDtorNonTrivial::dtor_invoked; +int CtorDtorNonTrivial::dtor_value; + +void ComponentLifecycle_dtor_w_non_trivial_implicit_move() { + flecs::world ecs; + + test_bool(std::is_trivially_move_assignable<CtorDtorNonTrivial>::value, false); + test_bool(std::is_move_assignable<CtorDtorNonTrivial>::value, true); + + auto e_1 = ecs.entity().emplace<CtorDtorNonTrivial>(10); + auto e_2 = ecs.entity().emplace<CtorDtorNonTrivial>(20); + + const CtorDtorNonTrivial *ptr = e_1.get<CtorDtorNonTrivial>(); + test_assert(ptr != nullptr); + test_int(ptr->x_, 10); + + ptr = e_2.get<CtorDtorNonTrivial>(); + test_assert(ptr != nullptr); + test_int(ptr->x_, 20); + + test_int(CtorDtorNonTrivial::ctor_invoked, 2); + + // Moves e_2 to e_1 + e_1.destruct(); + + test_int(CtorDtorNonTrivial::ctor_invoked, 2); + test_int(CtorDtorNonTrivial::dtor_invoked, 1); + + // Counter intuitive but correct. The class is not trivially movable, so + // the move assignment should take care of cleaning up e_1 (10). That still + // leaves the original e_2 which was moved from, but not destructed. + // + // In a real application the class should probably implement its own move + // assignment to ensure correct destructor behavior. + test_int(CtorDtorNonTrivial::dtor_value, 20); +} + +class CtorDtor_w_MoveAssign { +public: + CtorDtor_w_MoveAssign(int x) : x_(x) { + ctor_invoked ++; + } + + ~CtorDtor_w_MoveAssign() { + dtor_invoked ++; + dtor_value = x_; + } + + CtorDtor_w_MoveAssign(const CtorDtor_w_MoveAssign& obj) = default; + CtorDtor_w_MoveAssign(CtorDtor_w_MoveAssign&& obj) = default; + CtorDtor_w_MoveAssign& operator=(const CtorDtor_w_MoveAssign& obj) = default; + + CtorDtor_w_MoveAssign& operator=(CtorDtor_w_MoveAssign&& obj) { + move_value = this->x_; + + this->x_ = obj.x_; + obj.x_ = 0; + return *this; + } + + int x_; + std::string str_; + + static int ctor_invoked; + static int dtor_invoked; + static int dtor_value; + static int move_value; +}; + +int CtorDtor_w_MoveAssign::ctor_invoked; +int CtorDtor_w_MoveAssign::dtor_invoked; +int CtorDtor_w_MoveAssign::dtor_value; +int CtorDtor_w_MoveAssign::move_value; + +void ComponentLifecycle_dtor_w_non_trivial_explicit_move() { + flecs::world ecs; + + test_bool(std::is_trivially_move_assignable<CtorDtor_w_MoveAssign>::value, false); + test_bool(std::is_move_assignable<CtorDtor_w_MoveAssign>::value, true); + + auto e_1 = ecs.entity().emplace<CtorDtor_w_MoveAssign>(10); + auto e_2 = ecs.entity().emplace<CtorDtor_w_MoveAssign>(20); + + const CtorDtor_w_MoveAssign *ptr = e_1.get<CtorDtor_w_MoveAssign>(); + test_assert(ptr != nullptr); + test_int(ptr->x_, 10); + + ptr = e_2.get<CtorDtor_w_MoveAssign>(); + test_assert(ptr != nullptr); + test_int(ptr->x_, 20); + + test_int(CtorDtor_w_MoveAssign::ctor_invoked, 2); + + // Moves e_2 to e_1 + e_1.destruct(); + + test_int(CtorDtor_w_MoveAssign::ctor_invoked, 2); + test_int(CtorDtor_w_MoveAssign::dtor_invoked, 1); + + test_int(CtorDtor_w_MoveAssign::move_value, 10); + test_int(CtorDtor_w_MoveAssign::dtor_value, 0); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Entity.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Entity.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7b7cdcd439e88f86a7cb051ccd4c49a2c79c3c31 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Entity.cpp @@ -0,0 +1,2874 @@ +#include <cpp_api.h> + +void Entity_new() { + flecs::world world; + + auto entity = world.entity(); + test_assert(entity); +} + +void Entity_new_named() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo"); + test_assert(entity); + test_str(entity.name().c_str(), "Foo"); +} + +void Entity_new_named_from_scope() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo"); + test_assert(entity); + test_str(entity.name().c_str(), "Foo"); + + auto prev = world.set_scope(entity); + + auto child = world.entity("Bar"); + test_assert(child != 0); + + world.set_scope(prev); + + test_str(child.name().c_str(), "Bar"); + test_str(child.path().c_str(), "::Foo::Bar"); +} + +void Entity_new_nested_named_from_scope() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo"); + test_assert(entity); + test_str(entity.name().c_str(), "Foo"); + + auto prev = world.set_scope(entity); + + auto child = world.entity("Bar::Hello"); + test_assert(child != 0); + + world.set_scope(prev); + + test_str(child.name().c_str(), "Hello"); + test_str(child.path().c_str(), "::Foo::Bar::Hello"); +} + +void Entity_new_nested_named_from_nested_scope() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo::Bar"); + test_assert(entity); + test_str(entity.name().c_str(), "Bar"); + test_str(entity.path().c_str(), "::Foo::Bar"); + + auto prev = world.set_scope(entity); + + auto child = world.entity("Hello::World"); + test_assert(child != 0); + + world.set_scope(prev); + + test_str(child.name().c_str(), "World"); + test_str(child.path().c_str(), "::Foo::Bar::Hello::World"); +} + +void Entity_new_add() { + flecs::world world; + + world.component<Position>(); + + auto entity = world.entity() + .add<Position>(); + + test_assert(entity); + test_assert(entity.has<Position>()); +} + +void Entity_new_add_2() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto entity = world.entity() + .add<Position>() + .add<Velocity>(); + + test_assert(entity); + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); +} + +void Entity_new_set() { + flecs::world world; + + world.component<Position>(); + + auto entity = world.entity() + .set<Position>({10, 20}); + + test_assert(entity); + test_assert(entity.has<Position>()); + + const Position *p = entity.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_new_set_2() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + test_assert(entity); + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); + + const Position *p = entity.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = entity.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void Entity_add() { + flecs::world world; + + world.component<Position>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.add<Position>(); + test_assert(entity.has<Position>()); +} + +void Entity_remove() { + flecs::world world; + + world.component<Position>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.add<Position>(); + test_assert(entity.has<Position>()); + + entity.remove<Position>(); + test_assert(!entity.has<Position>()); +} + +void Entity_set() { + flecs::world world; + + world.component<Position>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.set<Position>({10, 20}); + test_assert(entity.has<Position>()); + + const Position *p = entity.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_emplace_2() { + flecs::world ecs; + + auto e = ecs.entity() + .emplace<Position>(10.0f, 20.0f) + .emplace<Velocity>(30.0f, 40.0f); + + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); +} + +void Entity_emplace_after_add() { + flecs::world ecs; + + auto e = ecs.entity() + .add<Position>() + .emplace<Velocity>(30.0f, 40.0f); + + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); +} + +void Entity_emplace_after_add_pair() { + flecs::world ecs; + + auto dummy = ecs.entity(); + + auto e = ecs.entity() + .add(flecs::ChildOf, dummy) + .emplace<Velocity>(30.0f, 40.0f); + + test_assert(e.has(flecs::ChildOf, dummy)); + test_assert(e.has<Velocity>()); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 30); + test_int(v->y, 40); +} + +class SelfCtor { +public: + SelfCtor(flecs::entity e, int x, int y) : e_(e), x_(x), y_(y) { } + + flecs::entity e_; + int x_; + int y_; +}; + +void Entity_emplace_w_self_ctor() { + flecs::world ecs; + + auto e = ecs.entity() + .emplace<SelfCtor>(10, 20); + + const SelfCtor *ptr = e.get<SelfCtor>(); + test_assert(ptr != NULL); + test_int(ptr->x_, 10); + test_int(ptr->y_, 20); + test_assert(ptr->e_ == e); +} + +void Entity_replace() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.patch<Position>([](Position& p) { + p.x = 10; + p.y = 20; + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); + + entity.patch<Position>([](Position& p_arg) { + p_arg.x = 30; + }); + + test_int(p->x, 30); +} + +void Entity_add_2() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.add<Position>() + .add<Velocity>(); + + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); +} + +void Entity_add_entity() { + flecs::world world; + + auto tag = world.entity(); + test_assert(tag != 0); + + auto entity = world.entity(); + test_assert(entity); + + entity.add(tag); + test_assert(entity.has(tag)); +} + +void Entity_add_childof() { + flecs::world world; + + auto parent = world.entity(); + test_assert(parent != 0); + + auto entity = world.entity(); + test_assert(entity); + + entity.add(flecs::ChildOf, parent); + test_assert(entity.has(flecs::ChildOf, parent)); +} + +void Entity_add_instanceof() { + flecs::world world; + + auto base = world.entity(); + test_assert(base != 0); + + auto entity = world.entity(); + test_assert(entity); + + entity.add(flecs::IsA, base); + test_assert(entity.has(flecs::IsA, base)); +} + +void Entity_remove_2() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.add<Position>() + .add<Velocity>(); + + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); + + entity.remove<Position>() + .remove<Velocity>(); + + test_assert(!entity.has<Position>()); + test_assert(!entity.has<Velocity>()); +} + +void Entity_set_2() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto entity = world.entity(); + test_assert(entity); + + entity.set<Position>({10, 20}) + .set<Velocity>({1, 2}); + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); + + const Position *p = entity.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = entity.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void Entity_remove_entity() { + flecs::world world; + + auto tag = world.entity(); + test_assert(tag != 0); + + auto entity = world.entity(); + test_assert(entity); + + entity.add(tag); + test_assert(entity.has(tag)); + + entity.remove(tag); + test_assert(!entity.has(tag)); +} + +void Entity_remove_childof() { + flecs::world world; + + auto parent = world.entity(); + test_assert(parent != 0); + + auto entity = world.entity(); + test_assert(entity); + + entity.add(flecs::ChildOf, parent); + test_assert(entity.has(flecs::ChildOf, parent)); + + entity.remove(flecs::ChildOf, parent); + test_assert(!entity.has(flecs::ChildOf, parent)); +} + +void Entity_remove_instanceof() { + flecs::world world; + + auto base = world.entity(); + test_assert(base != 0); + + auto entity = world.entity(); + test_assert(entity); + + entity.add(flecs::IsA, base); + test_assert(entity.has(flecs::IsA, base)); + + entity.remove(flecs::IsA, base); + test_assert(!entity.has(flecs::IsA, base)); +} + +void Entity_get_generic() { + flecs::world world; + + auto position = world.component<Position>(); + + auto entity = world.entity() + .set<Position>({10, 20}); + + test_assert(entity); + test_assert(entity.has<Position>()); + + const void *void_p = entity.get(position); + test_assert(void_p != nullptr); + + const Position *p = static_cast<const Position*>(void_p); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_get_mut_generic() { + flecs::world world; + + auto position = world.component<Position>(); + + auto entity = world.entity() + .set<Position>({10, 20}); + + test_assert(entity); + test_assert(entity.has<Position>()); + + bool invoked; + world.system<Position>() + .kind(flecs::OnSet) + .each([&invoked](flecs::entity e, Position& p) { + invoked = true; + }); + + void *void_p = entity.get_mut(position); + test_assert(void_p != nullptr); + + Position *p = static_cast<Position*>(void_p); + test_int(p->x, 10); + test_int(p->y, 20); + + entity.modified(position); + test_bool(invoked, true); +} + +void Entity_get_generic_w_id() { + flecs::world world; + + auto position = world.component<Position>(); + + auto entity = world.entity() + .set<Position>({10, 20}); + + test_assert(entity); + test_assert(entity.has<Position>()); + + const void *void_p = entity.get(position); + test_assert(void_p != nullptr); + + const Position *p = static_cast<const Position*>(void_p); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_get_mut_generic_w_id() { + flecs::world world; + + auto position = world.component<Position>(); + + auto entity = world.entity() + .set<Position>({10, 20}); + + test_assert(entity); + test_assert(entity.has<Position>()); + + bool invoked; + world.system<Position>() + .kind(flecs::OnSet) + .each([&invoked](flecs::entity e, Position& p) { + invoked = true; + }); + + void *void_p = entity.get_mut(position); + test_assert(void_p != nullptr); + + Position *p = static_cast<Position*>(void_p); + test_int(p->x, 10); + test_int(p->y, 20); + + entity.modified(position); + test_bool(invoked, true); +} + +void Entity_add_role() { + flecs::world world; + + auto entity = world.entity(); + + entity = entity.add_role(flecs::Pair); + + test_assert(entity & ECS_PAIR); +} + +void Entity_remove_role() { + flecs::world world; + + auto entity = world.entity(); + + flecs::entity_t id = entity; + + entity = entity.add_role(flecs::Pair); + + test_assert(entity & ECS_PAIR); + + entity = entity.remove_role(); + + test_assert(entity == id); +} + +void Entity_has_role() { + flecs::world world; + + auto entity = world.entity(); + + entity = entity.add_role(flecs::Pair); + + test_assert(entity.has_role(flecs::Pair)); + + entity = entity.remove_role(); + + test_assert(!entity.has_role(flecs::Pair)); +} + +void Entity_pair_role() { + flecs::world world; + + auto a = world.entity(); + auto b = world.entity(); + + auto comb = flecs::entity::comb(a, b); + comb = comb.add_role(flecs::Pair); + + test_assert(comb.has_role(flecs::Pair)); + + auto lo = comb.lo(); + auto hi = comb.remove_role().hi(); + + test_assert(lo == a); + test_assert(hi == b); +} + +void Entity_equals() { + flecs::world world; + + auto e1 = world.entity(); + auto e2 = world.entity(); + + auto e1_2 = world.entity(e1); + auto e2_2 = world.entity(e2); + + test_assert(e1 == e1_2); + test_assert(e2 == e2_2); + test_assert(e1 >= e1_2); + test_assert(e1 <= e1_2); + test_assert(e2 >= e2_2); + test_assert(e2 <= e2_2); + test_assert(e1 != e2); + + test_assert(!(e2 == e1_2)); + test_assert(!(e1 == e2_2)); + test_assert(!(e2 <= e1_2)); + test_assert(!(e1 >= e2_2)); + test_assert(!(e2 != e2)); +} + +void Entity_compare_0() { + flecs::world world; + + auto e = world.entity(); + auto e0 = world.entity(0); + auto e0_2 = world.entity(0); + + test_assert(e != e0); + test_assert(e > e0); + test_assert(e >= e0); + test_assert(e0 < e); + test_assert(e0 <= e); + + test_assert(e0 == e0_2); + test_assert(e0 >= e0_2); + test_assert(e0 <= e0_2); +} + +void Entity_compare_id_t() { + flecs::world world; + + auto e1 = world.entity(); + auto e2 = world.entity(); + + flecs::id_t id1 = e1; + flecs::id_t id2 = e2; + + test_assert(e1 == id1); + test_assert(e2 == id2); + + test_assert(e1 != id2); + test_assert(e2 != id1); + + test_assert(e1 >= id1); + test_assert(e2 >= id2); + + test_assert(e1 <= id1); + test_assert(e2 <= id2); + + test_assert(e1 <= id2); + test_assert(e2 >= id1); + + test_assert(e1 < id2); + test_assert(e2 > id1); + + + test_assert(!(e2 == id1)); + test_assert(!(e1 == id2)); + + test_assert(!(e2 != id2)); + test_assert(!(e1 != id1)); + + test_assert(!(e1 >= id2)); + test_assert(!(e2 <= id1)); + + test_assert(!(e2 < id2)); + test_assert(!(e1 > id1)); +} + +void Entity_compare_id() { + flecs::world world; + + auto e1 = world.entity(); + auto e2 = world.entity(); + + flecs::id id1 = e1; + flecs::id id2 = e2; + + test_assert(e1 == id1); + test_assert(e2 == id2); + + test_assert(e1 != id2); + test_assert(e2 != id1); + + test_assert(e1 >= id1); + test_assert(e2 >= id2); + + test_assert(e1 <= id1); + test_assert(e2 <= id2); + + test_assert(e1 <= id2); + test_assert(e2 >= id1); + + test_assert(e1 < id2); + test_assert(e2 > id1); + + + test_assert(!(e2 == id1)); + test_assert(!(e1 == id2)); + + test_assert(!(e2 != id2)); + test_assert(!(e1 != id1)); + + test_assert(!(e1 >= id2)); + test_assert(!(e2 <= id1)); + + test_assert(!(e2 < id2)); + test_assert(!(e1 > id1)); +} + +void Entity_compare_literal() { + flecs::world world; + + auto e1 = world.entity(500); + auto e2 = world.entity(600); + + test_assert(e1 == 500); + test_assert(e2 == 600); + + test_assert(e1 != 600); + test_assert(e2 != 500); + + test_assert(e1 >= 500); + test_assert(e2 >= 600); + + test_assert(e1 <= 500); + test_assert(e2 <= 600); + + test_assert(e1 <= 600); + test_assert(e2 >= 500); + + test_assert(e1 < 600); + test_assert(e2 > 500); + + + test_assert(!(e2 == 500)); + test_assert(!(e1 == 600)); + + test_assert(!(e2 != 600)); + test_assert(!(e1 != 500)); + + test_assert(!(e1 >= 600)); + test_assert(!(e2 <= 500)); + + test_assert(!(e2 < 600)); + test_assert(!(e1 > 500)); +} + +void Entity_greater_than() { + flecs::world world; + + auto e1 = world.entity(); + auto e2 = world.entity(); + + test_assert(e2 > e1); + test_assert(e2 >= e1); +} + +void Entity_less_than() { + flecs::world world; + + auto e1 = world.entity(); + auto e2 = world.entity(); + + test_assert(e1 < e2); + test_assert(e1 <= e2); +} + +void Entity_not_0_or_1() { + flecs::world world; + + auto e = world.entity(); + + flecs::id_t id = e; + + test_assert(id != 0); + test_assert(id != 1); +} + +void Entity_not_true_or_false() { + flecs::world world; + + auto e = world.entity(); + + flecs::id_t id = e; + + test_assert(id != true); + test_assert(id != false); +} + +void Entity_has_childof() { + flecs::world world; + + auto parent = world.entity(); + + auto e = world.entity() + .add(flecs::ChildOf, parent); + + test_assert(e.has(flecs::ChildOf, parent)); +} + +void Entity_has_instanceof() { + flecs::world world; + + auto base = world.entity(); + + auto e = world.entity() + .add(flecs::IsA, base); + + test_assert(e.has(flecs::IsA, base)); +} + +void Entity_has_instanceof_indirect() { + flecs::world world; + + auto base_of_base = world.entity(); + + auto base = world.entity() + .add(flecs::IsA, base_of_base); + + auto e = world.entity() + .add(flecs::IsA, base); + + test_assert(e.has(flecs::IsA, base_of_base)); +} + +void Entity_null_string() { + flecs::world world; + + auto e = world.entity(); + + test_str(e.name().c_str(), ""); +} + +void Entity_set_name() { + flecs::world world; + + auto e = world.entity(); + test_str(e.name().c_str(), ""); + + e.set_name("Foo"); + test_str(e.name().c_str(), "Foo"); +} + +void Entity_change_name() { + flecs::world world; + + auto e = world.entity("Bar"); + test_str(e.name().c_str(), "Bar"); + + e.set_name("Foo"); + test_str(e.name().c_str(), "Foo"); +} + +void Entity_delete() { + flecs::world world; + + auto e = world.entity() + .add<Position>() + .add<Velocity>(); + + e.destruct(); + + test_assert(!e.is_alive()); + + auto e2 = world.entity(); + + // Entity ids should be equal without the generation + test_assert((uint32_t)e2 == (uint32_t)e); + test_assert(e2 != e); +} + +void Entity_clear() { + flecs::world world; + + auto e = world.entity() + .add<Position>() + .add<Velocity>(); + + e.clear(); + + test_assert(!e.has<Position>()); + test_assert(!e.has<Velocity>()); + + auto e2 = world.entity(); + test_assert(e2 > e); +} + +void Entity_foce_owned() { + flecs::world world; + + auto prefab = world.prefab() + .add<Position>() + .add<Velocity>() + .add_owned<Position>(); + + auto e = world.entity() + .add(flecs::IsA, prefab); + + test_assert(e.has<Position>()); + test_assert(e.owns<Position>()); + test_assert(e.has<Velocity>()); + test_assert(!e.owns<Velocity>()); +} + +void Entity_force_owned_2() { + flecs::world world; + + auto prefab = world.prefab() + .add<Position>() + .add<Velocity>() + .add_owned<Position>() + .add_owned<Velocity>(); + + auto e = world.entity() + .add(flecs::IsA, prefab); + + test_assert(e.has<Position>()); + test_assert(e.owns<Position>()); + test_assert(e.has<Velocity>()); + test_assert(e.owns<Velocity>()); +} + +void Entity_force_owned_nested() { + flecs::world world; + + auto prefab = world.prefab() + .add<Position>() + .add<Velocity>() + .add_owned<Position>(); + + auto prefab_2 = world.prefab() + .add(flecs::IsA, prefab); + + auto e = world.entity() + .add(flecs::IsA, prefab_2); + + test_assert(e.has<Position>()); + test_assert(e.owns<Position>()); + test_assert(e.has<Velocity>()); + test_assert(!e.owns<Velocity>()); +} + +void Entity_force_owned_type() { + flecs::world world; + + auto type = world.type() + .add<Position>() + .add<Velocity>(); + + auto prefab = world.prefab() + .add<Position>() + .add<Velocity>() + .add<Rotation>() + .add_owned(type); + + auto e = world.entity() + .add(flecs::IsA, prefab); + + test_assert(e.has<Position>()); + test_assert(e.owns<Position>()); + test_assert(e.has<Velocity>()); + test_assert(e.owns<Velocity>()); + test_assert(e.has<Rotation>()); + test_assert(!e.owns<Rotation>()); +} + +void Entity_force_owned_type_w_pair() { + flecs::world world; + + auto type = world.type() + .add<Position, Velocity>() + .add<Velocity>(); + + auto prefab = world.prefab() + .add<Position, Velocity>() + .add<Rotation>() + .add_owned(type); + + auto e = world.entity() + .add(flecs::IsA, prefab); + + test_assert((e.has<Position, Velocity>())); + test_assert(e.has<Rotation>()); + test_assert(!e.owns<Rotation>()); + + const Position *pp = prefab.get<Position, Velocity>(); + const Position *p = e.get<Position, Velocity>(); + test_assert(pp != p); +} + +struct MyTag { }; + +void Entity_tag_has_size_zero() { + flecs::world world; + + auto comp = world.component<MyTag>(); + + auto ptr = comp.get<flecs::Component>(); + test_int(ptr->size, 0); + test_int(ptr->alignment, 0); +} + +void Entity_get_null_name() { + flecs::world world; + + auto e = world.entity().set_name(nullptr); + + auto n = e.name(); + test_assert(n.size() == 0); +} + +void Entity_get_parent() { + flecs::world world; + + auto Rel = world.entity(); + + auto obj1 = world.entity() + .add<Position>(); + + auto obj2 = world.entity() + .add<Velocity>(); + + auto obj3 = world.entity() + .add<Mass>(); + + auto child = world.entity() + .add(Rel, obj1) + .add(Rel, obj2) + .add(Rel, obj3); + + auto p = child.get_object(Rel); + test_assert(p != 0); + test_assert(p == obj1); + + p = child.get_object(Rel, 0); + test_assert(p != 0); + test_assert(p == obj1); + + p = child.get_object(Rel, 1); + test_assert(p != 0); + test_assert(p == obj2); + + p = child.get_object(Rel, 2); + test_assert(p != 0); + test_assert(p == obj3); + + p = child.get_object(Rel, 3); + test_assert(p == 0); +} + +void Entity_is_component_enabled() { + flecs::world world; + + auto e = world.entity() + .add<Position>(); + + test_assert(e.is_enabled<Position>()); +} + +void Entity_is_enabled_component_enabled() { + flecs::world world; + + auto e = world.entity() + .add<Position>() + .enable<Position>(); + + test_assert(e.is_enabled<Position>()); +} + +void Entity_is_disabled_component_enabled() { + flecs::world world; + + auto e = world.entity() + .add<Position>() + .disable<Position>(); + + test_assert(!e.is_enabled<Position>()); +} + +void Entity_get_type() { + flecs::world world; + + auto entity = world.entity(); + test_assert(entity); + + auto type_1 = entity.type(); + test_assert(type_1.id() == 0); + test_int(type_1.vector().size(), 0); + + auto type_2 = entity.type(); + test_assert(type_2.id() == 0); + test_int(type_1.vector().size(), 0); +} + +void Entity_get_nonempty_type() { + flecs::world world; + + auto entity = world.entity() + .add<Position>(); + test_assert(entity); + + auto type_1 = entity.type(); + test_assert(type_1.id() == 0); + test_int(type_1.vector().size(), 1); + + auto type_2 = entity.type(); + test_assert(type_2.id() == 0); + test_int(type_1.vector().size(), 1); +} + +void Entity_set_no_copy() { + flecs::world world; + + auto e = world.entity() + .set<Pod>({10}); + test_int(Pod::copy_invoked, 0); + + test_assert(e.has<Pod>()); + const Pod *p = e.get<Pod>(); + test_assert(p != NULL); + test_int(p->value, 10); +} + +void Entity_set_copy() { + flecs::world world; + + Pod val(10); + + auto e = world.entity() + .set<Pod>(val); + test_int(Pod::copy_invoked, 1); + + test_assert(e.has<Pod>()); + const Pod *p = e.get<Pod>(); + test_assert(p != NULL); + test_int(p->value, 10); +} + +void Entity_set_deduced() { + flecs::world world; + + auto e = world.entity() + .set(Position{10, 20}); + + test_assert(e.has<Position>()); + + const Position *p = e.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_add_owned() { + flecs::world world; + + auto base = world.entity() + .add_owned<Position>(); + + auto e = world.entity() + .add(flecs::IsA, base); + + test_assert(e.has<Position>()); + test_assert(e.owns<Position>()); +} + +void Entity_set_owned() { + flecs::world world; + + auto base = world.entity() + .set_owned<Position>({10, 20}); + + auto e = world.entity() + .add(flecs::IsA, base); + + test_assert(e.has<Position>()); + test_assert(e.owns<Position>()); + + const Position* p = e.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); + + const Position* p_base = base.get<Position>(); + test_assert(p != p_base); + test_int(p_base->x, 10); + test_int(p_base->y, 20); +} + +void Entity_implicit_name_to_char() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo"); + test_assert(entity); + test_str(entity.name().c_str(), "Foo"); + + test_str(entity.name(), "Foo"); +} + +void Entity_implicit_path_to_char() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo::Bar"); + test_assert(entity); + test_str(entity.name().c_str(), "Bar"); + + test_str(entity.path(), "::Foo::Bar"); +} + +void Entity_implicit_type_str_to_char() { + flecs::world world; + + auto entity = flecs::entity(world, "Foo"); + test_assert(entity); + + test_str(entity.type().str(), "(Identifier,Name)"); +} + +void Entity_entity_to_entity_view() { + flecs::world world; + + flecs::entity e = world.entity() + .set<Position>({10, 20}); + test_assert(e != 0); + + flecs::entity_view ev = e; + test_assert(ev != 0); + test_assert(e == ev); + + const Position *p = ev.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_entity_view_to_entity_world() { + flecs::world world; + + flecs::entity e = world.entity() + .set<Position>({10, 20}); + test_assert(e != 0); + + flecs::entity_view ev = e; + test_assert(ev != 0); + test_assert(e == ev); + + flecs::entity ew = ev.mut(world); + ew.set<Position>({10, 20}); + + test_assert(ev.has<Position>()); + const Position *p = ev.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_entity_view_to_entity_stage() { + flecs::world world; + + flecs::entity_view ev = world.entity(); + + auto stage = world.get_stage(0); + + world.staging_begin(); + + flecs::entity ew = ev.mut(stage); + + ew.set<Position>({10, 20}); + test_assert(!ew.has<Position>()); + + world.staging_end(); + + test_assert(ew.has<Position>()); + test_assert(ev.has<Position>()); + + const Position *p = ev.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_create_entity_view_from_stage() { + flecs::world world; + + auto stage = world.get_stage(0); + + world.staging_begin(); + + flecs::entity_view ev = stage.entity(); + test_assert(ev != 0); + + world.staging_end(); + + // Ensure we can use created ev out of stage + auto ew = ev.mut(world); + ew.set<Position>({10, 20}); + test_assert(ev.has<Position>()); + + const Position *p = ev.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_set_template() { + flecs::world ecs; + + auto e = ecs.entity() + .set<Template<int>>({10, 20}); + + const Template<int> *ptr = e.get<Template<int>>(); + test_int(ptr->x, 10); + test_int(ptr->y, 20); +} + +void Entity_get_1_component_w_callback() { + flecs::world ecs; + + auto e_1 = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + auto e_2 = ecs.entity() + .set<Position>({11, 22}); + + auto e_3 = ecs.entity() + .set<Velocity>({1, 2}); + + test_bool(e_1.get([](const Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + }), true); + + test_bool(e_2.get([](const Position& p) { + test_int(p.x, 11); + test_int(p.y, 22); + }), true); + + test_bool(e_3.get([](const Position& p) {}), false); +} + +void Entity_get_2_components_w_callback() { + flecs::world ecs; + + auto e_1 = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + auto e_2 = ecs.entity() + .set<Position>({11, 22}); + + auto e_3 = ecs.entity() + .set<Velocity>({1, 2}); + + test_bool(e_1.get([](const Position& p, const Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + }), true); + + test_bool(e_2.get([](const Position& p, const Velocity& v) {}), false); + + test_bool(e_3.get([](const Position& p, const Velocity& v) {}), false); +} + +void Entity_set_1_component_w_callback() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](Position& p){ + p.x = 10; + p.y = 20; + }); + + test_assert(e.has<Position>()); + + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_set_2_components_w_callback() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](Position& p, Velocity& v){ + p = {10, 20}; + v = {1, 2}; + }); + + test_assert(e.has<Position>()); + + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void Entity_set_3_components_w_callback() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](Position& p, Velocity& v, Mass& m){ + p = {10, 20}; + v = {1, 2}; + m = {50}; + }); + + test_assert(e.has<Position>()); + + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 2); + + const Mass *m = e.get<Mass>(); + test_assert(m != NULL); + test_int(m->value, 50); +} + +void Entity_defer_set_1_component() { + flecs::world ecs; + + ecs.defer_begin(); + + auto e = ecs.entity() + .set([](Position& p){ + p.x = 10; + p.y = 20; + }); + + test_assert(!e.has<Position>()); + + ecs.defer_end(); + + test_assert(e.has<Position>()); + + e.get([](const Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + }); +} + +void Entity_defer_set_2_components() { + flecs::world ecs; + + ecs.defer_begin(); + + auto e = ecs.entity() + .set([](Position& p, Velocity& v){ + p = {10, 20}; + v = {1, 2}; + }); + + test_assert(!e.has<Position>()); + test_assert(!e.has<Velocity>()); + + ecs.defer_end(); + + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + + e.get([](const Position& p, const Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + }); +} + +void Entity_defer_set_3_components() { + flecs::world ecs; + + ecs.defer_begin(); + + auto e = ecs.entity() + .set([](Position& p, Velocity& v, Mass& m){ + p = {10, 20}; + v = {1, 2}; + m = {50}; + }); + + test_assert(!e.has<Position>()); + test_assert(!e.has<Velocity>()); + test_assert(!e.has<Mass>()); + + ecs.defer_end(); + + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + test_assert(e.has<Mass>()); + + e.get([](const Position& p, const Velocity& v, const Mass& m) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + + test_int(m.value, 50); + }); +} + +void Entity_set_2_w_on_set() { + flecs::world ecs; + + int32_t position_set = 0; + int32_t velocity_set = 0; + + ecs.system<Position>() + .kind(flecs::OnSet) + .each([&](flecs::entity e, Position& p) { + position_set ++; + test_int(p.x, 10); + test_int(p.y, 20); + }); + + ecs.system<Velocity>() + .kind(flecs::OnSet) + .each([&](flecs::entity e, Velocity& v) { + velocity_set ++; + test_int(v.x, 1); + test_int(v.y, 2); + }); + + auto e = ecs.entity() + .set([](Position& p, Velocity& v){ + p = {10, 20}; + v = {1, 2}; + }); + + test_int(position_set, 1); + test_int(velocity_set, 1); + + test_bool(e.get([](const Position& p, const Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + }), true); +} + +void Entity_defer_set_2_w_on_set() { + flecs::world ecs; + + int32_t position_set = 0; + int32_t velocity_set = 0; + + ecs.system<Position>() + .kind(flecs::OnSet) + .each([&](flecs::entity e, Position& p) { + position_set ++; + test_int(p.x, 10); + test_int(p.y, 20); + }); + + ecs.system<Velocity>() + .kind(flecs::OnSet) + .each([&](flecs::entity e, Velocity& v) { + velocity_set ++; + test_int(v.x, 1); + test_int(v.y, 2); + }); + + ecs.defer_begin(); + + auto e = ecs.entity() + .set([](Position& p, Velocity& v){ + p = {10, 20}; + v = {1, 2}; + }); + + test_int(position_set, 0); + test_int(velocity_set, 0); + + ecs.defer_end(); + + test_int(position_set, 1); + test_int(velocity_set, 1); + + test_bool(e.get([](const Position& p, const Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + }), true); +} + +void Entity_set_2_after_fluent() { + flecs::world ecs; + + auto e = ecs.entity() + .set<Mass>({50}) + .set([](Position& p, Velocity& v){ + p = {10, 20}; + v = {1, 2}; + }); + + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + test_assert(e.has<Mass>()); + + test_bool(e.get([](const Position& p, const Velocity& v, const Mass& m) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + + test_int(m.value, 50); + }), true); +} + +void Entity_set_2_before_fluent() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](Position& p, Velocity& v){ + p = {10, 20}; + v = {1, 2}; + }) + .set<Mass>({50}); + + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + test_assert(e.has<Mass>()); + + test_bool(e.get([](const Position& p, const Velocity& v, const Mass& m) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + + test_int(m.value, 50); + }), true); +} + +void Entity_set_2_after_set_1() { + flecs::world ecs; + + int called = 0; + + auto e = ecs.entity().set<Position>({5, 10}); + test_assert(e.has<Position>()); + + test_bool(e.get([](const Position& p) { + test_int(p.x, 5); + test_int(p.y, 10); + }), true); + + // Set both Position and Velocity + e.set([](Position& p, Velocity& v) { + p = {10, 20}; + v = {1, 2}; + }); + + test_bool(e.get([&](const Position& p, const Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 1); + test_int(v.y, 2); + + called ++; + }), true); + + test_int(called, 1); +} + +void Entity_set_2_after_set_2() { + flecs::world ecs; + + int called = 0; + + auto e = ecs.entity() + .set<Position>({5, 10}) + .set<Velocity>({1, 2}); + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + + test_bool(e.get([&](const Position& p, const Velocity& v) { + test_int(p.x, 5); + test_int(p.y, 10); + + test_int(v.x, 1); + test_int(v.y, 2); + + called ++; + }), true); + + test_int(called, 1); + + // Set both Position and Velocity (doesn't add any components) + e.set([](Position& p, Velocity& v) { + p = {10, 20}; + v = {3, 4}; + }); + + test_bool(e.get([&](const Position& p, const Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(v.x, 3); + test_int(v.y, 4); + + called ++; + }), true); + + test_int(called, 2); +} + +void Entity_with_self() { + flecs::world ecs; + + auto Tag = ecs.entity().with([&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get the tag. + auto self = ecs.component<Self>(); + test_assert(!self.has(Tag)); + + int count = 0; + auto q = ecs.query_builder<>().term(Tag).build(); + + q.each([&](flecs::entity e) { + test_assert(e.has(Tag)); + + test_bool(e.get([&](const Self& s){ + test_assert(s.value == e); + }), true); + + count ++; + }); + + test_int(count, 3); +} + +void Entity_with_relation_type_self() { + flecs::world ecs; + + struct Likes { }; + + auto Bob = ecs.entity().with<Likes>([&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get the tag. + auto self = ecs.component<Self>(); + test_assert(!self.has<Likes>(Bob)); + + int count = 0; + auto q = ecs.query_builder<>().term<Likes>(Bob).build(); + + q.each([&](flecs::entity e) { + test_assert(e.has<Likes>(Bob)); + + test_bool(e.get([&](const Self& s){ + test_assert(s.value == e); + }), true); + + count ++; + }); + + test_int(count, 3); +} + +void Entity_with_relation_self() { + flecs::world ecs; + + auto Likes = ecs.entity(); + + auto Bob = ecs.entity().with(Likes, [&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get the tag. + auto self = ecs.component<Self>(); + test_assert(!self.has(Likes, Bob)); + + int count = 0; + auto q = ecs.query_builder<>().term(Likes, Bob).build(); + + q.each([&](flecs::entity e) { + test_assert(e.has(Likes, Bob)); + + test_bool(e.get([&](const Self& s){ + test_assert(s.value == e); + }), true); + + count ++; + }); + + test_int(count, 3); +} + +void Entity_with_self_w_name() { + flecs::world ecs; + + auto Tier1 = ecs.entity("Tier1").with([&]{ + auto Tier2 = ecs.entity("Tier2"); + Tier2.set<Self>({Tier2}); + }); + + auto Tier2 = ecs.lookup("Tier2"); + test_assert(Tier2 != 0); + + test_assert(Tier2.has(Tier1)); +} + +void Entity_with_self_nested() { + flecs::world ecs; + + auto Tier1 = ecs.entity("Tier1").with([&]{ + ecs.entity("Tier2").with([&]{ + ecs.entity("Tier3"); + }); + }); + + auto Tier2 = ecs.lookup("Tier2"); + test_assert(Tier2 != 0); + + auto Tier3 = ecs.lookup("Tier3"); + test_assert(Tier3 != 0); + + test_assert(Tier2.has(Tier1)); + test_assert(Tier3.has(Tier2)); +} + +void Entity_with_scope() { + flecs::world ecs; + + auto parent = ecs.entity("P").scope([&]{ + auto e1 = ecs.entity("C1"); e1.set<Self>({e1}); + auto e2 = ecs.entity("C2"); e2.set<Self>({e2}); + auto e3 = ecs.entity("C3"); e3.set<Self>({e3}); + + // Ensure relative lookups work + test_assert(ecs.lookup("C1") == e1); + test_assert(ecs.lookup("C2") == e2); + test_assert(ecs.lookup("C3") == e3); + + test_assert(ecs.lookup("::P::C1") == e1); + test_assert(ecs.lookup("::P::C2") == e2); + test_assert(ecs.lookup("::P::C3") == e3); + }); + + // Ensure entities are created in correct scope + test_assert(ecs.lookup("C1") == 0); + test_assert(ecs.lookup("C2") == 0); + test_assert(ecs.lookup("C3") == 0); + + test_assert(parent.lookup("C1") != 0); + test_assert(parent.lookup("C2") != 0); + test_assert(parent.lookup("C3") != 0); + + test_assert(ecs.lookup("P::C1") == parent.lookup("C1")); + test_assert(ecs.lookup("P::C2") == parent.lookup("C2")); + test_assert(ecs.lookup("P::C3") == parent.lookup("C3")); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not become a child of the parent. + auto self = ecs.component<Self>(); + test_assert(!self.has(flecs::ChildOf, parent)); + + int count = 0; + auto q = ecs.query_builder<>().term(flecs::ChildOf, parent).build(); + + q.each([&](flecs::entity e) { + test_assert(e.has(flecs::ChildOf, parent)); + + test_bool(e.get([&](const Self& s){ + test_assert(s.value == e); + }), true); + + count ++; + }); + + test_int(count, 3); +} + +void Entity_with_scope_nested() { + flecs::world ecs; + + auto parent = ecs.entity("P").scope([&]{ + auto child = ecs.entity("C").scope([&]{ + auto gchild = ecs.entity("GC"); + test_assert(gchild == ecs.lookup("GC")); + test_assert(gchild == ecs.lookup("::P::C::GC")); + }); + + // Ensure relative lookups work + test_assert(ecs.lookup("C") == child); + test_assert(ecs.lookup("::P::C") == child); + test_assert(ecs.lookup("::P::C::GC") != 0); + }); + + test_assert(0 == ecs.lookup("C")); + test_assert(0 == ecs.lookup("GC")); + test_assert(0 == ecs.lookup("C::GC")); + + auto child = ecs.lookup("P::C"); + test_assert(0 != child); + test_assert(child.has(flecs::ChildOf, parent)); + + auto gchild = ecs.lookup("P::C::GC"); + test_assert(0 != gchild); + test_assert(gchild.has(flecs::ChildOf, child)); +} + +void Entity_with_scope_nested_same_name_as_parent() { + flecs::world ecs; + + auto parent = ecs.entity("P").scope([&]{ + auto child = ecs.entity("C").scope([&]{ + auto gchild = ecs.entity("C"); + test_assert(gchild == ecs.lookup("C")); + test_assert(gchild == ecs.lookup("::P::C::C")); + }); + + // Ensure relative lookups work + test_assert(ecs.lookup("C") == child); + test_assert(ecs.lookup("::P::C") == child); + test_assert(ecs.lookup("::P::C::C") != 0); + }); + + test_assert(0 == ecs.lookup("C")); + test_assert(0 == ecs.lookup("C")); + test_assert(0 == ecs.lookup("C::C")); + + auto child = ecs.lookup("P::C"); + test_assert(0 != child); + test_assert(child.has(flecs::ChildOf, parent)); + + auto gchild = ecs.lookup("P::C::C"); + test_assert(0 != gchild); + test_assert(gchild.has(flecs::ChildOf, child)); +} + +void Entity_no_recursive_lookup() { + flecs::world ecs; + + auto p = ecs.entity("P"); + auto c = ecs.entity("C").child_of(p); + auto gc = ecs.entity("GC").child_of(c); + + test_assert(c.lookup("GC") == gc); + test_assert(c.lookup("C") == 0); + test_assert(c.lookup("P") == 0); +} + +void Entity_defer_new_w_name() { + flecs::world ecs; + + flecs::entity e; + + ecs.defer([&]{ + e = ecs.entity("Foo"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Foo"); +} + +void Entity_defer_new_w_nested_name() { + flecs::world ecs; + + flecs::entity e; + + ecs.defer([&]{ + e = ecs.entity("Foo::Bar"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(0 == ecs.lookup("Bar")); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Bar"); + test_str(e.path(), "::Foo::Bar"); +} + + +void Entity_defer_new_w_scope_name() { + flecs::world ecs; + + flecs::entity e, parent = ecs.entity("Parent"); + + ecs.defer([&]{ + parent.scope([&]{ + e = ecs.entity("Foo"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + }); + + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Foo"); + test_str(e.path(), "::Parent::Foo"); +} + +void Entity_defer_new_w_scope_nested_name() { + flecs::world ecs; + + flecs::entity e, parent = ecs.entity("Parent"); + + ecs.defer([&]{ + parent.scope([&]{ + e = ecs.entity("Foo::Bar"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(0 == ecs.lookup("Bar")); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + }); + + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Bar"); + test_str(e.path(), "::Parent::Foo::Bar"); +} + +void Entity_defer_new_w_deferred_scope_nested_name() { + flecs::world ecs; + + flecs::entity e, parent; + + ecs.defer([&]{ + parent = ecs.entity("Parent").scope([&]{ + e = ecs.entity("Foo::Bar"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(0 == ecs.lookup("Bar")); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + + test_assert(0 == ecs.lookup("Parent")); + test_assert(0 == ecs.lookup("Foo")); + test_assert(0 == ecs.lookup("Bar")); + test_assert(!parent.has<flecs::Identifier>(flecs::Name)); + }); + + test_assert(parent.has<flecs::Identifier>(flecs::Name)); + test_str(parent.name(), "Parent"); + test_str(parent.path(), "::Parent"); + + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Bar"); + test_str(e.path(), "::Parent::Foo::Bar"); +} + +void Entity_defer_new_w_scope() { + flecs::world ecs; + + flecs::entity e, parent = ecs.entity(); + + ecs.defer([&]{ + parent.scope([&]{ + e = ecs.entity(); + test_assert(e != 0); + }); + }); + + test_assert(e.has(flecs::ChildOf, parent)); +} + +void Entity_defer_new_w_with() { + flecs::world ecs; + + flecs::entity e, Tag = ecs.entity(); + + ecs.defer([&]{ + Tag.with([&]{ + e = ecs.entity(); + test_assert(e != 0); + test_assert(!e.has(Tag)); + }); + }); + + test_assert(e.has(Tag)); +} + +void Entity_defer_new_w_name_scope_with() { + flecs::world ecs; + + flecs::entity e, Tag = ecs.entity(), parent = ecs.entity("Parent"); + + ecs.defer([&]{ + Tag.with([&]{ + parent.scope([&]{ + e = ecs.entity("Foo"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has(Tag)); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has(Tag)); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has(Tag)); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + + test_assert(e.has(Tag)); + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Foo"); + test_str(e.path(), "::Parent::Foo"); +} + +void Entity_defer_new_w_nested_name_scope_with() { + flecs::world ecs; + + flecs::entity e, Tag = ecs.entity(), parent = ecs.entity("Parent"); + + ecs.defer([&]{ + Tag.with([&]{ + parent.scope([&]{ + e = ecs.entity("Foo::Bar"); + test_assert(e != 0); + test_assert(0 == ecs.lookup("Foo")); + test_assert(0 == ecs.lookup("Bar")); + test_assert(!e.has(Tag)); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has(Tag)); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + test_assert(0 == ecs.lookup("Foo")); + test_assert(!e.has(Tag)); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); + }); + + test_assert(e.has(Tag)); + test_assert(e.has<flecs::Identifier>(flecs::Name)); + test_str(e.name(), "Bar"); + test_str(e.path(), "::Parent::Foo::Bar"); +} + +void Entity_defer_w_with_implicit_component() { + flecs::world ecs; + + struct Tag { }; + + flecs::entity e; + + ecs.defer([&]{ + ecs.with<Tag>([&]{ + e = ecs.entity(); + test_assert(!e.has<Tag>()); + }); + test_assert(!e.has<Tag>()); + }); + + test_assert(e.has<Tag>()); +} + +void Entity_with_after_builder_method() { + flecs::world ecs; + + struct Likes { }; + + auto A = ecs.entity() + .set<Position>({10, 20}) + .with([&]{ + ecs.entity("X"); + }); + + auto B = ecs.entity().set<Position>({30, 40}) + .with<Likes>([&]{ + ecs.entity("Y"); + }); + + auto C = ecs.entity().set<Position>({50, 60}) + .with(flecs::IsA, [&]{ + ecs.entity("Z"); + }); + + test_assert(A.get([](const Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + })); + + test_assert(B.get([](const Position& p) { + test_int(p.x, 30); + test_int(p.y, 40); + })); + + test_assert(C.get([](const Position& p) { + test_int(p.x, 50); + test_int(p.y, 60); + })); + + auto X = ecs.lookup("X"); + test_assert(X != 0); + test_assert(X.has(A)); + + auto Y = ecs.lookup("Y"); + test_assert(Y != 0); + test_assert(Y.has<Likes>(B)); + + auto Z = ecs.lookup("Z"); + test_assert(Z != 0); + test_assert(Z.has(flecs::IsA, C)); +} + +void Entity_with_before_builder_method() { + flecs::world ecs; + + struct Likes { }; + + auto A = ecs.entity() + .with([&]{ + ecs.entity("X"); + }) + .set<Position>({10, 20}); + + auto B = ecs.entity().with<Likes>([&]{ + ecs.entity("Y"); + }) + .set<Position>({30, 40}); + + auto C = ecs.entity().with(flecs::IsA, [&]{ + ecs.entity("Z"); + }) + .set<Position>({50, 60}); + + test_assert(A.get([](const Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + })); + + test_assert(B.get([](const Position& p) { + test_int(p.x, 30); + test_int(p.y, 40); + })); + + test_assert(C.get([](const Position& p) { + test_int(p.x, 50); + test_int(p.y, 60); + })); + + auto X = ecs.lookup("X"); + test_assert(X != 0); + test_assert(X.has(A)); + + auto Y = ecs.lookup("Y"); + test_assert(Y != 0); + test_assert(Y.has<Likes>(B)); + + auto Z = ecs.lookup("Z"); + test_assert(Z != 0); + test_assert(Z.has(flecs::IsA, C)); +} + +void Entity_scope_after_builder_method() { + flecs::world ecs; + + ecs.entity("P") + .set<Position>({10, 20}) + .scope([&]{ + ecs.entity("C"); + }); + + auto C = ecs.lookup("P::C"); + test_assert(C != 0); +} + +void Entity_scope_before_builder_method() { + flecs::world ecs; + + ecs.entity("P") + .scope([&]{ + ecs.entity("C"); + }) + .set<Position>({10, 20}); + + auto C = ecs.lookup("P::C"); + test_assert(C != 0); +} + +void Entity_emplace() { + flecs::world ecs; + + auto e = ecs.entity() + .emplace<Position>(10.0f, 20.0f); + + test_assert(e.has<Position>()); + + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Entity_entity_id_str() { + flecs::world ecs; + + flecs::id id = ecs.entity("Foo"); + + test_str("Foo", id.str()); +} + +void Entity_pair_id_str() { + flecs::world ecs; + + flecs::id id = ecs.pair( ecs.entity("Rel"), ecs.entity("Obj") ); + + test_str("(Rel,Obj)", id.str()); +} + +void Entity_role_id_str() { + flecs::world ecs; + + flecs::id id = flecs::id(ecs, ECS_SWITCH | ecs.entity("Foo")); + + test_str("SWITCH|Foo", id.str()); +} + +void Entity_id_str_from_entity_view() { + flecs::world ecs; + + flecs::entity_view id = ecs.entity("Foo"); + + test_str("Foo", id.str()); +} + +void Entity_id_str_from_entity() { + flecs::world ecs; + + flecs::entity id = ecs.entity("Foo"); + + test_str("Foo", id.str()); +} + +void Entity_null_entity() { + flecs::entity e = flecs::entity::null(); + test_assert(e.id() == 0); +} + +void Entity_null_entity_w_world() { + flecs::world ecs; + + flecs::entity e = flecs::entity::null(ecs); + test_assert(e.id() == 0); + test_assert(e.world().c_ptr() == ecs.c_ptr()); +} + +void Entity_null_entity_w_0() { + flecs::entity e = flecs::entity(static_cast<flecs::id_t>(0)); + test_assert(e.id() == 0); + test_assert(e.world().c_ptr() == nullptr); +} + +void Entity_null_entity_w_world_w_0() { + flecs::world ecs; + + flecs::entity e = flecs::entity(ecs, static_cast<flecs::id_t>(0)); + test_assert(e.id() == 0); + test_assert(e.world().c_ptr() == ecs.c_ptr()); +} + +void Entity_entity_view_null_entity() { + flecs::entity_view e = flecs::entity::null(); + test_assert(e.id() == 0); +} + +void Entity_entity_view_null_entity_w_world() { + flecs::world ecs; + + flecs::entity_view e = flecs::entity::null(ecs); + test_assert(e.id() == 0); + test_assert(e.world().c_ptr() == ecs.c_ptr()); +} + +void Entity_entity_view_null_entity_w_0() { + flecs::entity_view e = flecs::entity(static_cast<flecs::id_t>(0)); + test_assert(e.id() == 0); + test_assert(e.world().c_ptr() == nullptr); +} + +void Entity_entity_view_null_entity_w_world_w_0() { + flecs::world ecs; + + flecs::entity_view e = flecs::entity(ecs, static_cast<flecs::id_t>(0)); + test_assert(e.id() == 0); + test_assert(e.world().c_ptr() == ecs.c_ptr()); +} + +void Entity_is_wildcard() { + flecs::world ecs; + + auto e1 = ecs.entity(); + auto e2 = ecs.entity(); + + auto p0 = e1; + auto p1 = ecs.pair(e1, e2); + auto p2 = ecs.pair(e1, flecs::Wildcard); + auto p3 = ecs.pair(flecs::Wildcard, e2); + auto p4 = ecs.pair(flecs::Wildcard, flecs::Wildcard); + + test_bool(e1.is_wildcard(), false); + test_bool(e2.is_wildcard(), false); + test_bool(p0.is_wildcard(), false); + test_bool(p1.is_wildcard(), false); + test_bool(p2.is_wildcard(), true); + test_bool(p3.is_wildcard(), true); + test_bool(p4.is_wildcard(), true); +} + +void Entity_has_id_t() { + flecs::world ecs; + + flecs::id_t id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id_t id_2 = ecs.entity(); + test_assert(id_2 != 0); + + auto e = ecs.entity() + .add(id_1); + + test_assert(e != 0); + test_bool(e.has(id_1), true); + test_bool(e.has(id_2), false); +} + +void Entity_has_pair_id_t() { + flecs::world ecs; + + flecs::id_t id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id_t id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id_t id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add(id_1, id_2); + + test_assert(e != 0); + test_bool(e.has(id_1, id_2), true); + test_bool(e.has(id_1, id_3), false); +} + +void Entity_has_pair_id_t_w_type() { + flecs::world ecs; + + struct Rel { }; + + flecs::id_t id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id_t id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add<Rel>(id_2); + + test_assert(e != 0); + test_bool(e.has<Rel>(id_2), true); + test_bool(e.has<Rel>(id_3), false); +} + +void Entity_has_id() { + flecs::world ecs; + + flecs::id id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id id_2 = ecs.entity(); + test_assert(id_2 != 0); + + auto e = ecs.entity() + .add(id_1); + + test_assert(e != 0); + test_bool(e.has(id_1), true); + test_bool(e.has(id_2), false); +} + +void Entity_has_pair_id() { + flecs::world ecs; + + flecs::id id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add(id_1, id_2); + + test_assert(e != 0); + test_bool(e.has(id_1, id_2), true); + test_bool(e.has(id_1, id_3), false); +} + +void Entity_has_pair_id_w_type() { + flecs::world ecs; + + struct Rel { }; + + flecs::id id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add<Rel>(id_2); + + test_assert(e != 0); + test_bool(e.has<Rel>(id_2), true); + test_bool(e.has<Rel>(id_3), false); +} + +void Entity_has_wildcard_id() { + flecs::world ecs; + + flecs::id id = ecs.entity(); + test_assert(id != 0); + + auto e1 = ecs.entity().add(id); + auto e2 = ecs.entity(); + + test_assert(e1 != 0); + test_assert(e2 != 0); + + test_bool(e1.has(flecs::Wildcard), true); + test_bool(e2.has(flecs::Wildcard), false); +} + +void Entity_has_wildcard_pair_id() { + flecs::world ecs; + + flecs::id rel = ecs.entity(); + test_assert(rel != 0); + + flecs::id obj = ecs.entity(); + test_assert(obj != 0); + + flecs::id obj_2 = ecs.entity(); + test_assert(obj_2 != 0); + + flecs::id w1 = ecs.id(rel, flecs::Wildcard); + flecs::id w2 = ecs.id(flecs::Wildcard, obj); + + auto e1 = ecs.entity().add(rel, obj); + auto e2 = ecs.entity().add(rel, obj_2); + + test_assert(e1 != 0); + test_assert(e2 != 0); + + test_bool(e1.has(w1), true); + test_bool(e1.has(w2), true); + + test_bool(e2.has(w1), true); + test_bool(e2.has(w2), false); +} + +void Entity_owns_id_t() { + flecs::world ecs; + + flecs::id_t id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id_t id_2 = ecs.entity(); + test_assert(id_2 != 0); + + auto e = ecs.entity() + .add(id_1); + + test_assert(e != 0); + test_bool(e.owns(id_1), true); + test_bool(e.owns(id_2), false); +} + +void Entity_owns_pair_id_t() { + flecs::world ecs; + + flecs::id_t id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id_t id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id_t id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add(id_1, id_2); + + test_assert(e != 0); + test_bool(e.owns(id_1, id_2), true); + test_bool(e.owns(id_1, id_3), false); +} + +void Entity_owns_pair_id_t_w_type() { + flecs::world ecs; + + struct Rel { }; + + flecs::id_t id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id_t id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add<Rel>(id_2); + + test_assert(e != 0); + test_bool(e.owns<Rel>(id_2), true); + test_bool(e.owns<Rel>(id_3), false); +} + +void Entity_owns_id() { + flecs::world ecs; + + flecs::id id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id id_2 = ecs.entity(); + test_assert(id_2 != 0); + + auto e = ecs.entity() + .add(id_1); + + test_assert(e != 0); + test_bool(e.owns(id_1), true); + test_bool(e.owns(id_2), false); +} + +void Entity_owns_pair_id() { + flecs::world ecs; + + flecs::id id_1 = ecs.entity(); + test_assert(id_1 != 0); + + flecs::id id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add(id_1, id_2); + + test_assert(e != 0); + test_bool(e.owns(id_1, id_2), true); + test_bool(e.owns(id_1, id_3), false); +} + +void Entity_owns_wildcard_id() { + flecs::world ecs; + + flecs::id id = ecs.entity(); + test_assert(id != 0); + + auto e1 = ecs.entity().add(id); + auto e2 = ecs.entity(); + + test_assert(e1 != 0); + test_assert(e2 != 0); + + test_bool(e1.owns(flecs::Wildcard), true); + test_bool(e2.owns(flecs::Wildcard), false); +} + +void Entity_owns_wildcard_pair() { + flecs::world ecs; + + flecs::id rel = ecs.entity(); + test_assert(rel != 0); + + flecs::id obj = ecs.entity(); + test_assert(obj != 0); + + flecs::id obj_2 = ecs.entity(); + test_assert(obj_2 != 0); + + flecs::id w1 = ecs.id(rel, flecs::Wildcard); + flecs::id w2 = ecs.id(flecs::Wildcard, obj); + + auto e1 = ecs.entity().add(rel, obj); + auto e2 = ecs.entity().add(rel, obj_2); + + test_assert(e1 != 0); + test_assert(e2 != 0); + + test_bool(e1.owns(w1), true); + test_bool(e1.owns(w2), true); + + test_bool(e2.owns(w1), true); + test_bool(e2.owns(w2), false); +} + +void Entity_owns_pair_id_w_type() { + flecs::world ecs; + + struct Rel { }; + + flecs::id id_2 = ecs.entity(); + test_assert(id_2 != 0); + + flecs::id id_3 = ecs.entity(); + test_assert(id_3 != 0); + + auto e = ecs.entity() + .add<Rel>(id_2); + + test_assert(e != 0); + test_bool(e.owns<Rel>(id_2), true); + test_bool(e.owns<Rel>(id_3), false); +} + +void Entity_id_from_world() { + flecs::world ecs; + + auto e = ecs.entity(); + test_assert(e != 0); + + flecs::id id_1 = ecs.id(e); + test_assert(id_1 != 0); + test_assert(id_1 == e); + test_assert(id_1.world() == ecs); + test_bool(id_1.is_pair(), false); + test_bool(id_1.is_wildcard(), false); + + flecs::id id_2 = ecs.id(flecs::Wildcard); + test_assert(id_2 != 0); + test_assert(id_2 == flecs::Wildcard); + test_assert(id_2.world() == ecs); + test_bool(id_2.is_pair(), false); + test_bool(id_2.is_wildcard(), true); +} + +void Entity_id_pair_from_world() { + flecs::world ecs; + + auto rel = ecs.entity(); + test_assert(rel != 0); + + auto obj = ecs.entity(); + test_assert(obj != 0); + + flecs::id id_1 = ecs.id(rel, obj); + test_assert(id_1 != 0); + test_assert(id_1.relation() == rel); + test_assert(id_1.object() == obj); + test_assert(id_1.world() == ecs); + test_bool(id_1.is_pair(), true); + test_bool(id_1.is_wildcard(), false); + + flecs::id id_2 = ecs.id(rel, flecs::Wildcard); + test_assert(id_2 != 0); + test_assert(id_2.relation() == rel); + test_assert(id_2.object() == flecs::Wildcard); + test_assert(id_2.world() == ecs); + test_bool(id_2.is_pair(), true); + test_bool(id_2.is_wildcard(), true); +} + +void Entity_id_default_from_world() { + flecs::world ecs; + + flecs::id id_default = ecs.id(); + test_assert(id_default == 0); +} + +void Entity_is_a() { + flecs::world world; + + auto base = world.entity(); + + auto e = world.entity().is_a(base); + + test_assert(e.has(flecs::IsA, base)); +} + +void Entity_is_a_w_type() { + flecs::world world; + + struct Prefab { }; + + auto base = world.entity().component<Prefab>(); + + auto e = world.entity().is_a<Prefab>(); + + test_assert(e.has(flecs::IsA, base)); + test_assert(e.has_w_object<Prefab>(flecs::IsA)); +} + +void Entity_child_of() { + flecs::world world; + + auto base = world.entity(); + + auto e = world.entity().child_of(base); + + test_assert(e.has(flecs::ChildOf, base)); +} + +void Entity_child_of_w_type() { + flecs::world world; + + struct Parent { }; + + auto base = world.entity().component<Parent>(); + + auto e = world.entity().child_of<Parent>(); + + test_assert(e.has(flecs::ChildOf, base)); + test_assert(e.has_w_object<Parent>(flecs::ChildOf)); +} + +void Entity_id_get_entity() { + flecs::world world; + + auto e = world.entity(); + + auto id = world.id(e); + + test_assert(id.entity() == e); +} + +void Entity_id_get_invalid_entity() { + install_test_abort(); + + flecs::world world; + + auto r = world.entity(); + auto o = world.entity(); + + auto id = world.id(r, o); + + test_expect_abort(); + + id.entity(); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Filter.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Filter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..76f75c55ad5f8bd60c58467b4d54406d5e805420 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Filter.cpp @@ -0,0 +1,164 @@ +#include <cpp_api.h> + +void Filter_term_each_component() { + flecs::world ecs; + + auto e_1 = ecs.entity().set<Position>({1, 2}); + auto e_2 = ecs.entity().set<Position>({3, 4}); + auto e_3 = ecs.entity().set<Position>({5, 6}); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each<Position>([&](flecs::entity e, Position& p) { + if (e == e_1) { + test_int(p.x, 1); + test_int(p.y, 2); + count ++; + } + if (e == e_2) { + test_int(p.x, 3); + test_int(p.y, 4); + count ++; + } + if (e == e_3) { + test_int(p.x, 5); + test_int(p.y, 6); + count ++; + } + }); + + test_int(count, 3); +} + +void Filter_term_each_tag() { + flecs::world ecs; + + struct Foo { }; + + auto e_1 = ecs.entity().add<Foo>(); + auto e_2 = ecs.entity().add<Foo>(); + auto e_3 = ecs.entity().add<Foo>(); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each<Foo>([&](flecs::entity e, Foo&) { + if (e == e_1 || e == e_2 || e == e_3) { + count ++; + } + }); + + test_int(count, 3); +} + +void Filter_term_each_id() { + flecs::world ecs; + + auto foo = ecs.entity(); + + auto e_1 = ecs.entity().add(foo); + auto e_2 = ecs.entity().add(foo); + auto e_3 = ecs.entity().add(foo); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each(foo, [&](flecs::entity e) { + if (e == e_1 || e == e_2 || e == e_3) { + count ++; + } + }); + + test_int(count, 3); +} + +void Filter_term_each_pair_type() { + flecs::world ecs; + + struct Rel { }; + struct Obj { }; + + auto e_1 = ecs.entity().add<Rel, Obj>(); + auto e_2 = ecs.entity().add<Rel, Obj>(); + auto e_3 = ecs.entity().add<Rel, Obj>(); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each<flecs::pair<Rel, Obj>>([&](flecs::entity e, flecs::pair<Rel,Obj>) { + if (e == e_1 || e == e_2 || e == e_3) { + count ++; + } + }); + + test_int(count, 3); +} + +void Filter_term_each_pair_id() { + flecs::world ecs; + + auto rel = ecs.entity(); + auto obj = ecs.entity(); + + auto e_1 = ecs.entity().add(rel, obj); + auto e_2 = ecs.entity().add(rel, obj); + auto e_3 = ecs.entity().add(rel, obj); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each(ecs.pair(rel, obj), [&](flecs::entity e) { + if (e == e_1 || e == e_2 || e == e_3) { + count ++; + } + }); + + test_int(count, 3); +} + +void Filter_term_each_pair_relation_wildcard() { + flecs::world ecs; + + auto rel_1 = ecs.entity(); + auto rel_2 = ecs.entity(); + auto obj = ecs.entity(); + + auto e_1 = ecs.entity().add(rel_1, obj); + auto e_2 = ecs.entity().add(rel_1, obj); + auto e_3 = ecs.entity().add(rel_2, obj); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each(ecs.pair(flecs::Wildcard, obj), [&](flecs::entity e) { + if (e == e_1 || e == e_2 || e == e_3) { + count ++; + } + }); + + test_int(count, 3); +} + +void Filter_term_each_pair_object_wildcard() { + flecs::world ecs; + + auto rel = ecs.entity(); + auto obj_1 = ecs.entity(); + auto obj_2 = ecs.entity(); + + auto e_1 = ecs.entity().add(rel, obj_1); + auto e_2 = ecs.entity().add(rel, obj_1); + auto e_3 = ecs.entity().add(rel, obj_2); + + e_3.add<Tag>(); + + int32_t count = 0; + ecs.each(ecs.pair(rel, flecs::Wildcard), [&](flecs::entity e) { + if (e == e_1 || e == e_2 || e == e_3) { + count ++; + } + }); + + test_int(count, 3); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/FilterBuilder.cpp b/fggl/ecs2/flecs/test/cpp_api/src/FilterBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b18cfddf5bf3c8c778f5de1d1b9fb814fcdf4efc --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/FilterBuilder.cpp @@ -0,0 +1,1591 @@ +#include <cpp_api.h> + +struct Other { + int32_t value; +}; + +void FilterBuilder_builder_assign_same_type() { + flecs::world ecs; + + flecs::filter<Position, Velocity> q = + ecs.filter_builder<Position, Velocity>(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_builder_assign_to_empty() { + flecs::world ecs; + + flecs::filter<> q = ecs.filter_builder<Position, Velocity>(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_builder_assign_from_empty() { + flecs::world ecs; + + flecs::filter<> q = ecs.filter_builder<>() + .term<Position>() + .term<Velocity>(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_builder_build() { + flecs::world ecs; + + flecs::filter<Position, Velocity> q = + ecs.filter_builder<Position, Velocity>().build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_builder_build_to_auto() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position, Velocity>().build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_builder_build_n_statements() { + flecs::world ecs; + + auto qb = ecs.filter_builder<>(); + qb.term<Position>(); + qb.term<Velocity>(); + auto q = qb.build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_1_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position>().build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_1_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<>() + .term<Position>() + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_2_types() { + flecs::world ecs; + + auto q = ecs.filter_builder<>() + .term<Position>() + .term<Velocity>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_1_type_w_1_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position>() + .term<Velocity>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_2_types_w_1_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position>() + .term<Velocity>() + .term<Mass>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>().add<Mass>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_pair() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.filter_builder<>() + .term(Likes, Bob) + .build(); + + auto e1 = ecs.entity().add(Likes, Bob); + ecs.entity().add(Likes, Alice); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_not() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position>() + .term<Velocity>().oper(flecs::Not) + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_add_or() { + flecs::world ecs; + + auto q = ecs.filter_builder<>() + .term<Position>().oper(flecs::Or) + .term<Velocity>().oper(flecs::Or) + .build(); + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Velocity>(); + ecs.entity().add<Mass>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 2); +} + +void FilterBuilder_add_optional() { + flecs::world ecs; + + auto q = ecs.filter_builder<>() + .term<Position>() + .term<Velocity>().oper(flecs::Optional) + .build(); + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>().add<Mass>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 2); +} + +void FilterBuilder_ptr_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position, Velocity*>().build(); + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>().add<Mass>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity* v) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 2); +} + +void FilterBuilder_const_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<const Position>().build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, const Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_string_term() { + flecs::world ecs; + + ecs.component<Position>(); + + auto q = ecs.filter_builder<>() + .term("Position") + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_singleton_term() { + flecs::world ecs; + + ecs.set<Other>({10}); + + auto q = ecs.filter_builder<Self>() + .term<Other>().singleton() + .build(); + + auto + e = ecs.entity(); e.set<Self>({e}); + e = ecs.entity(); e.set<Self>({e}); + e = ecs.entity(); e.set<Self>({e}); + + int32_t count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + test_assert(!o.is_owned()); + test_int(o->value, 10); + + const Other& o_ref = *o; + test_int(o_ref.value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 3); +} + +void FilterBuilder_isa_superset_term() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self>() + .term<Other>().subject().set(flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + + int32_t count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + test_assert(!o.is_owned()); + test_int(o->value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 3); +} + +void FilterBuilder_isa_self_superset_term() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self>() + .term<Other>().subject().set(flecs::Self | flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + + int32_t count = 0; + int32_t owned_count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + + if (!o.is_owned()) { + test_int(o->value, 10); + } else { + for (auto i : it) { + test_int(o[i].value, 20); + owned_count ++; + } + } + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 5); + test_int(owned_count, 2); +} + +void FilterBuilder_childof_superset_term() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self>() + .term<Other>().subject().set(flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + + int32_t count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + test_assert(!o.is_owned()); + test_int(o->value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 3); +} + +void FilterBuilder_childof_self_superset_term() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self>() + .term<Other>().subject().set(flecs::Self | flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + + int32_t count = 0; + int32_t owned_count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + + if (!o.is_owned()) { + test_int(o->value, 10); + } else { + for (auto i : it) { + test_int(o[i].value, 20); + owned_count ++; + } + } + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 5); + test_int(owned_count, 2); +} + +void FilterBuilder_isa_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).subject().set(flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_isa_self_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).subject().set(flecs::Self | flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void FilterBuilder_childof_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).subject().set(flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_childof_self_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).subject().set(flecs::Self | flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void FilterBuilder_isa_superset_shortcut() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset() + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_isa_superset_shortcut_w_self() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset(flecs::IsA, flecs::Self) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void FilterBuilder_childof_superset_shortcut() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset(flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_childof_superset_shortcut_w_self() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset(flecs::ChildOf, flecs::Self) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void FilterBuilder_isa_superset_max_depth_1() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset().max_depth(1) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + + e = ecs.entity().is_a(base_2); e.set<Self>({0}); + e = ecs.entity().is_a(base_2); e.set<Self>({0}); + + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 2); +} + +void FilterBuilder_isa_superset_max_depth_2() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset().max_depth(2) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 4); +} + +void FilterBuilder_isa_superset_min_depth_2() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset().min_depth(2) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + + e = ecs.entity().is_a(base_4); e.set<Self>({e}); + e = ecs.entity().is_a(base_4); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 6); +} + +void FilterBuilder_isa_superset_min_depth_2_max_depth_3() { + flecs::world ecs; + + auto q = ecs.filter_builder<Self, Other>() + .arg(2).superset().min_depth(2).max_depth(3) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 4); +} + +void FilterBuilder_relation() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.filter_builder<Self>() + .term(Likes, Bob) + .build(); + + auto + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + + e = ecs.entity().add(Likes, Alice); e.set<Self>({0}); + e = ecs.entity().add(Likes, Alice); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void FilterBuilder_relation_w_object_wildcard() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.filter_builder<Self>() + .term(Likes, flecs::Wildcard) + .build(); + + auto + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + + e = ecs.entity().add(Likes, Alice); e.set<Self>({e}); + e = ecs.entity().add(Likes, Alice); e.set<Self>({e}); + + e = ecs.entity(); e.set<Self>({0}); + e = ecs.entity(); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 4); +} + +void FilterBuilder_relation_w_predicate_wildcard() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Dislikes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.filter_builder<Self>() + .term(flecs::Wildcard, Alice) + .build(); + + auto + e = ecs.entity().add(Likes, Alice); e.set<Self>({e}); + e = ecs.entity().add(Dislikes, Alice); e.set<Self>({e}); + + e = ecs.entity().add(Likes, Bob); e.set<Self>({0}); + e = ecs.entity().add(Dislikes, Bob); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void FilterBuilder_add_pair_w_rel_type() { + flecs::world ecs; + + struct Likes { }; + + auto Dislikes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.filter_builder<Self>() + .term<Likes>(flecs::Wildcard) + .build(); + + auto + e = ecs.entity().add<Likes>(Alice); e.set<Self>({e}); + e = ecs.entity().add(Dislikes, Alice); e.set<Self>({0}); + + e = ecs.entity().add<Likes>(Bob); e.set<Self>({e}); + e = ecs.entity().add(Dislikes, Bob); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void FilterBuilder_template_term() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position>() + .term<Template<int>>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Template<int>>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_subject_w_id() { + flecs::world ecs; + + auto q = ecs.filter_builder<Position>() + .term<Position>().entity(flecs::This) + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_subject_w_type() { + flecs::world ecs; + + ecs.set<Position>({10, 20}); + + auto q = ecs.filter_builder<Position>() + .term<Position>().subject<Position>() + .build(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + count ++; + test_assert(e == ecs.singleton<Position>()); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_object_w_id() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Alice = ecs.entity(); + auto Bob = ecs.entity(); + + auto q = ecs.filter_builder<>() + .term(Likes).object(Alice) + .build(); + + auto e1 = ecs.entity().add(Likes, Alice); + ecs.entity().add(Likes, Bob); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_object_w_type() { + flecs::world ecs; + + auto Likes = ecs.entity(); + struct Alice { }; + auto Bob = ecs.entity(); + + auto q = ecs.filter_builder<>() + .term(Likes).object<Alice>() + .build(); + + auto e1 = ecs.entity().add(Likes, ecs.id<Alice>()); + ecs.entity().add(Likes, Bob); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_term() { + flecs::world ecs; + + auto q = ecs.filter_builder<>() + .term(ecs.term().id<Position>()) + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_term_w_type() { + flecs::world ecs; + + auto q = ecs.filter_builder<>() + .term(ecs.term<Position>()) + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_term_w_pair_type() { + flecs::world ecs; + + struct Likes { }; + struct Alice { }; + struct Bob { }; + + auto q = ecs.filter_builder<>() + .term(ecs.term<Likes, Alice>()) + .build(); + + auto e1 = ecs.entity().add<Likes, Alice>(); + ecs.entity().add<Likes, Bob>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_term_w_id() { + flecs::world ecs; + + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + + auto q = ecs.filter_builder<>() + .term(ecs.term(Apples)) + .build(); + + auto e1 = ecs.entity().add(Apples); + ecs.entity().add(Pears); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_explicit_term_w_pair_id() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + + auto q = ecs.filter_builder<>() + .term(ecs.term(Likes, Apples)) + .build(); + + auto e1 = ecs.entity().add(Likes, Apples); + ecs.entity().add(Likes, Pears); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void FilterBuilder_1_term_to_empty() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Apples = ecs.entity(); + + auto qb = ecs.filter_builder<>() + .term<Position>(); + + qb.term(Likes, Apples); + + auto q = qb.build(); + + test_int(q.term_count(), 2); + test_int(q.term(0).id(), ecs.id<Position>()); + test_int(q.term(1).id(), ecs.pair(Likes, Apples)); +} + +void FilterBuilder_2_subsequent_args() { + flecs::world ecs; + + struct Rel { int foo; }; + + int32_t count = 0; + + auto s = ecs.system<Rel, const Velocity>() + .arg(1).object(flecs::Wildcard) + .arg(2).singleton() + .iter([&](flecs::iter it){ + count += it.count(); + }); + + ecs.entity().add<Rel, Tag>(); + ecs.set<Velocity>({}); + + s.run(); + + test_int(count, 1); +} + +int filter_arg(flecs::filter<Self> f) { + int32_t count = 0; + + f.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + return count; +} + +void FilterBuilder_filter_as_arg() { + flecs::world ecs; + + auto f = ecs.filter<Self>(); + + auto e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + test_int(filter_arg(f), 3); +} + +int filter_move_arg(flecs::filter<Self>&& f) { + int32_t count = 0; + + f.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + return count; +} + +void FilterBuilder_filter_as_move_arg() { + flecs::world ecs; + + auto f = ecs.filter<Self>(); + + auto e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + test_int(filter_move_arg(ecs.filter<Self>()), 3); +} + +flecs::filter<Self> filter_return(flecs::world& ecs) { + return ecs.filter<Self>(); +} + +void FilterBuilder_filter_as_return() { + flecs::world ecs; + + auto e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + auto f = filter_return(ecs); + + int32_t count = 0; + + f.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_filter_copy() { + flecs::world ecs; + + auto e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + auto f = ecs.filter<Self>(); + + auto f_2 = f; + + int32_t count = 0; + + f_2.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_world_each_filter_1_component() { + flecs::world ecs; + + auto e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + e = ecs.entity(); + e.set<Self>({e}); + + int32_t count = 0; + + ecs.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_world_each_filter_2_components() { + flecs::world ecs; + + auto e = ecs.entity(); + e.set<Self>({e}) + .set<Position>({10, 20}); + + e = ecs.entity(); + e.set<Self>({e}) + .set<Position>({10, 20}); + + e = ecs.entity(); + e.set<Self>({e}) + .set<Position>({10, 20}); + + int32_t count = 0; + + ecs.each([&](flecs::entity e, Self& s, Position& p) { + test_assert(e == s.value); + test_int(p.x, 10); + test_int(p.y, 20); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_world_each_filter_1_component_no_entity() { + flecs::world ecs; + + ecs.entity() + .set<Position>({10, 20}); + + ecs.entity() + .set<Position>({10, 20}); + + ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + int32_t count = 0; + + ecs.each([&](Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_world_each_filter_2_components_no_entity() { + flecs::world ecs; + + ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.entity() + .set<Position>({3, 5}); + + ecs.entity() + .set<Velocity>({20, 40}); + + int32_t count = 0; + + ecs.each([&](Position& p, Velocity& v) { + test_int(p.x, 10); + test_int(p.y, 20); + test_int(v.x, 1); + test_int(v.y, 2); + count ++; + }); + + test_int(count, 3); +} + +void FilterBuilder_10_terms() { + flecs::world ecs; + + auto f = ecs.filter_builder<>() + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .build(); + + test_int(f.term_count(), 10); + + auto e = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>(); + + int count = 0; + f.iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 10); + count ++; + }); + + test_int(count, 1); +} + +void FilterBuilder_20_terms() { + flecs::world ecs; + + auto f = ecs.filter_builder<>() + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .term<TagK>() + .term<TagL>() + .term<TagM>() + .term<TagN>() + .term<TagO>() + .term<TagP>() + .term<TagQ>() + .term<TagR>() + .term<TagS>() + .term<TagT>() + .build(); + + test_int(f.term_count(), 20); + + auto e = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>() + .add<TagK>() + .add<TagL>() + .add<TagM>() + .add<TagN>() + .add<TagO>() + .add<TagP>() + .add<TagQ>() + .add<TagR>() + .add<TagS>() + .add<TagT>(); + + int count = 0; + f.iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 20); + count ++; + }); + + test_int(count, 1); +} + +void FilterBuilder_term_after_arg() { + flecs::world ecs; + + auto e_1 = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>(); + + ecs.entity() + .add<TagA>() + .add<TagB>(); + + auto f = ecs.filter_builder<TagA, TagB>() + .arg(1).subject(flecs::This) // dummy + .term<TagC>() + .build(); + + test_int(f.term_count(), 3); + + int count = 0; + f.each([&](flecs::entity e, TagA&, TagB&) { + test_assert(e == e_1); + count ++; + }); + + test_int(count, 1); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/ImplicitComponents.cpp b/fggl/ecs2/flecs/test/cpp_api/src/ImplicitComponents.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e5ac8f6b2c1f4ca23f33dce8566cd9491d8cf07 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/ImplicitComponents.cpp @@ -0,0 +1,448 @@ +#include <cpp_api.h> + +struct Pair { + int value; +}; + +void ImplicitComponents_add() { + flecs::world world; + + auto e = world.entity().add<Position>(); + + test_str(e.type().str().c_str(), "Position"); + test_assert(e.has<Position>()); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); +} + +void ImplicitComponents_remove() { + flecs::world world; + + auto e = world.entity().remove<Position>(); + + test_assert(!e.has<Position>()); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); +} + +void ImplicitComponents_has() { + flecs::world world; + + auto e = world.entity(); + test_assert(!e.has<Position>()); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); +} + +void ImplicitComponents_set() { + flecs::world world; + + auto e = world.entity().set<Position>({10, 20}); + + test_str(e.type().str().c_str(), "Position"); + test_assert(e.has<Position>()); + auto *p = e.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); +} + +void ImplicitComponents_get() { + flecs::world world; + + auto e = world.entity(); + + auto *p = e.get<Position>(); + test_assert(p == nullptr); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); +} + +void ImplicitComponents_add_pair() { + flecs::world world; + + auto e = world.entity().add<Pair, Position>(); + + test_str(e.type().str().c_str(), "(Pair,Position)"); + test_assert((e.has<Pair, Position>())); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); + + auto pair = world.lookup("Pair"); + test_assert(pair.id() != 0); +} + +void ImplicitComponents_remove_pair() { + flecs::world world; + + auto e = world.entity().remove<Position, Pair>(); + + test_assert((!e.has<Position, Pair>())); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); + + auto pair = world.lookup("Pair"); + test_assert(pair.id() != 0); +} + +void ImplicitComponents_module() { + flecs::world world; + + world.module<Position>(); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); +} + +void ImplicitComponents_system() { + flecs::world world; + + world.system<Position, Velocity>() + .each([](flecs::entity e, Position& p, Velocity& v) { + }); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); + + auto velocity = world.lookup("Velocity"); + test_assert(velocity.id() != 0); +} + +void ImplicitComponents_system_optional() { + flecs::world world; + + int rotation_count = 0; + int mass_count = 0; + + world.system<Rotation*, Mass*>() + .each([&](flecs::entity e, Rotation* r, Mass* m) { + if (r) { + rotation_count ++; + } + if (m) { + mass_count ++; + } + }); + + world.entity().set<Rotation>({10}); + world.entity().set<Mass>({20}); + + world.entity() + .set<Rotation>({30}) + .set<Mass>({40}); + + auto rotation = world.lookup("Rotation"); + test_assert(rotation.id() != 0); + + auto mass = world.lookup("Mass"); + test_assert(mass.id() != 0); + + auto rcomp = world.component<Rotation>(); + test_assert(rcomp == rotation); + + auto mcomp = world.component<Mass>(); + test_assert(mcomp == mass); + + world.progress(); + + test_int(rotation_count, 2); + test_int(mass_count, 2); +} + +void ImplicitComponents_system_const() { + flecs::world world; + + int count = 0; + world.system<Position, const Velocity>() + .each([&](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + count ++; + }); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); + + auto velocity = world.lookup("Velocity"); + test_assert(velocity.id() != 0); + + auto e = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + auto pcomp = world.component<Position>(); + test_assert(pcomp == position); + + auto vcomp = world.component<Velocity>(); + test_assert(vcomp == velocity); + + world.progress(); + + test_int(count, 1); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void ImplicitComponents_query() { + flecs::world world; + + auto q = world.query<Position, Velocity>(); + + q.each([](flecs::entity e, Position& p, Velocity &v) { }); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); + + auto velocity = world.lookup("Velocity"); + test_assert(velocity.id() != 0); +} + +void ImplicitComponents_implicit_name() { + flecs::world world; + + auto pcomp = world.component<Position>(); + + auto position = world.lookup("Position"); + test_assert(position.id() != 0); + + test_assert(pcomp == position); +} + +void ImplicitComponents_reinit() { + flecs::world world; + + auto comp_1 = world.component<Position>(); + + test_assert(flecs::type_id<Position>() == comp_1.id()); + + // Reset component id using internals (currently the only way to simulate + // registration across translation units) + flecs::_::cpp_type<Position>::reset(); + + world.entity() + .add<Position>(); + + test_assert(flecs::type_id<Position>() == comp_1.id()); +} + +namespace Foo { + struct Position { + float x; + float y; + }; +} + +void ImplicitComponents_reinit_scoped() { + flecs::world world; + + auto comp_1 = world.component<Foo::Position>(); + + test_assert(flecs::type_id<Foo::Position>() == comp_1.id()); + + // Reset component id using internals (currently the only way to simulate + // registration across translation units) + flecs::_::cpp_type<Foo::Position>::reset(); + + world.entity() + .add<Foo::Position>(); + + test_assert(flecs::type_id<Foo::Position>() == comp_1.id()); +} + +static int position_ctor_invoked = 0; + +ECS_CTOR(Position, ptr, { + position_ctor_invoked ++; +}); + +void ImplicitComponents_reinit_w_lifecycle() { + flecs::world world; + + auto comp_1 = world.pod_component<Position>(); + + test_assert(flecs::type_id<Position>() == comp_1.id()); + + // Explicitly register constructor + EcsComponentLifecycle cl{}; + cl.ctor = ecs_ctor(Position); + ecs_set_component_actions_w_entity(world.c_ptr(), comp_1.id(), &cl); + + auto e = world.entity() + .add<Position>(); + test_assert(e.has<Position>()); + test_int(position_ctor_invoked, 1); + + // Reset component id using internals (currently the only way to simulate + // registration across translation units) + flecs::_::cpp_type<Position>::reset(); + + e = world.entity() + .add<Position>(); + test_assert(e.has<Position>()); + test_int(position_ctor_invoked, 2); + + test_assert(flecs::type_id<Position>() == comp_1.id()); +} + +void ImplicitComponents_first_use_in_system() { + flecs::world world; + + world.system<Position>() + .each([](flecs::entity e, Position& p) { + e.add<Velocity>(); + }); + + auto e = world.entity().add<Position>(); + + world.progress(); + + test_assert(e.has<Velocity>()); +} + +namespace ns { + struct NsTag { }; +} + +void ImplicitComponents_first_use_tag_in_system() { + flecs::world world; + + world.system<Position>() + .each([](flecs::entity e, Position& p) { + e.add<Tag>(); + e.add<ns::NsTag>(); + }); + + auto e = world.entity().add<Position>(); + + world.progress(); + + test_assert(e.has<Tag>()); +} + +void ImplicitComponents_use_const() { + flecs::world world; + + world.use<const Position>(); + + auto e = world.entity() + .set<Position>({10, 20}); + + test_assert(e.has<Position>()); + + const Position *p = e.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void ImplicitComponents_use_const_w_stage() { + flecs::world world; + + world.use<const Velocity>(); + + auto e = world.entity() + .set<Position>({10, 20}); + + world.system<Position>() + .each([](flecs::entity e, Position&) { + e.set<Velocity>({1, 2}); + }); + + world.progress(); + + test_assert(e.has<Velocity>()); + + const Velocity *v = e.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void ImplicitComponents_use_const_w_threads() { + bake_set_os_api(); + + flecs::world world; + + world.use<const Velocity>(); + + auto e = world.entity() + .set<Position>({10, 20}); + + world.system<Position>() + .each([](flecs::entity e, Position&) { + e.set<Velocity>({1, 2}); + }); + + world.set_threads(2); + + world.progress(); + + test_assert(e.has<Velocity>()); + + const Velocity *v = e.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void ImplicitComponents_implicit_base() { + flecs::world world; + + auto v = world.use<Position>(); + + test_int(v.id(), flecs::type_id<Position>()); + test_int(v.id(), flecs::type_id<const Position>()); + test_int(v.id(), flecs::type_id<Position*>()); + test_int(v.id(), flecs::type_id<Position&>()); +} + +void ImplicitComponents_implicit_const() { + flecs::world world; + + auto v = world.use<const Position>(); + + test_int(v.id(), flecs::type_id<Position>()); + test_int(v.id(), flecs::type_id<const Position>()); + test_int(v.id(), flecs::type_id<Position*>()); + test_int(v.id(), flecs::type_id<Position&>()); +} + +void ImplicitComponents_implicit_ref() { + flecs::world world; + + auto v = world.use<Position&>(); + + test_int(v.id(), flecs::type_id<Position>()); + test_int(v.id(), flecs::type_id<const Position>()); + test_int(v.id(), flecs::type_id<Position*>()); + test_int(v.id(), flecs::type_id<Position&>()); +} + +void ImplicitComponents_implicit_ptr() { + flecs::world world; + + auto v = world.use<Position*>(); + + test_int(v.id(), flecs::type_id<Position>()); + test_int(v.id(), flecs::type_id<const Position>()); + test_int(v.id(), flecs::type_id<Position*>()); + test_int(v.id(), flecs::type_id<Position&>()); +} + +void ImplicitComponents_implicit_const_ref() { + flecs::world world; + + auto v = world.use<const Position&>(); + + test_int(v.id(), flecs::type_id<Position>()); + test_int(v.id(), flecs::type_id<const Position>()); + test_int(v.id(), flecs::type_id<Position*>()); + test_int(v.id(), flecs::type_id<Position&>()); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Misc.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Misc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..81f44209ab40a8b26bb1127ad0acd6f167b7631d --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Misc.cpp @@ -0,0 +1,111 @@ +#include <cpp_api.h> + +void Misc_setup() { + ecs_os_set_api_defaults(); +} + +void Misc_string_compare_w_char_ptr() { + const char *ptr_1 = "foo"; + const char *ptr_2 = "foo"; + const char *ptr_3 = "bar"; + + flecs::string str = flecs::string(ecs_os_strdup(ptr_1)); + + test_assert(str == ptr_1); + test_assert(str == ptr_2); + test_assert(str != ptr_3); +} + +void Misc_string_view_compare_w_char_ptr() { + const char *ptr_1 = "foo"; + const char *ptr_2 = "foo"; + const char *ptr_3 = "bar"; + + flecs::string str = flecs::string_view(ptr_1); + + test_assert(str == ptr_1); + test_assert(str == ptr_2); + test_assert(str != ptr_3); +} + +void Misc_string_compare_w_char_ptr_length_diff() { + const char *ptr_1 = "foo"; + const char *ptr_2 = "foo"; + const char *ptr_3 = "barrr"; + const char *ptr_4 = "ba"; + + flecs::string str = flecs::string_view(ptr_1); + + test_assert(str == ptr_1); + test_assert(str == ptr_2); + test_assert(str != ptr_3); + test_assert(str != ptr_4); +} + +void Misc_string_compare_w_string() { + const char *ptr_1 = "foo"; + const char *ptr_2 = "bar"; + + flecs::string str_1 = flecs::string(ecs_os_strdup(ptr_1)); + flecs::string str_2 = flecs::string(ecs_os_strdup(ptr_2)); + + flecs::string str_3 = flecs::string(ecs_os_strdup(ptr_1)); + flecs::string str_4 = flecs::string(ecs_os_strdup(ptr_2)); + + test_assert(str_1 == str_1); + test_assert(str_1 == str_3); + + test_assert(str_2 == str_2); + test_assert(str_2 == str_4); + + test_assert(str_1 != str_2); + test_assert(str_2 != str_1); +} + +void Misc_string_view_compare_w_string() { + const char *ptr_1 = "foo"; + const char *ptr_2 = "bar"; + + flecs::string str_1 = flecs::string_view(ptr_1); + flecs::string str_2 = flecs::string_view(ptr_2); + + flecs::string str_3 = flecs::string(ecs_os_strdup(ptr_1)); + flecs::string str_4 = flecs::string(ecs_os_strdup(ptr_2)); + + test_assert(str_1 == str_1); + test_assert(str_1 == str_3); + + test_assert(str_2 == str_2); + test_assert(str_2 == str_4); + + test_assert(str_1 != str_2); + test_assert(str_2 != str_1); +} + +void Misc_string_compare_nullptr() { + const char *ptr_1 = "foo"; + + flecs::string str = flecs::string_view(ptr_1); + + test_assert(str != nullptr); +} + +void Misc_nullptr_string_compare() { + const char *ptr = "foo"; + + flecs::string str_1 = flecs::string_view(nullptr); + flecs::string str_2 = flecs::string_view(ptr); + + test_assert(str_1 == ""); + test_assert(str_1 != ptr); + test_assert(str_1 != str_2); +} + +void Misc_nullptr_string_compare_nullptr() { + const char *ptr_1 = "foo"; + + flecs::string str = flecs::string_view(nullptr); + + test_assert(str == ""); + test_assert(str != ptr_1); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Module.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d426a2e1ef2a4baddf66106d076302f8de2b4999 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Module.cpp @@ -0,0 +1,214 @@ +#include <cpp_api.h> + +namespace ns { +struct NestedNameSpaceType { }; + +class NestedModule { +public: + NestedModule(flecs::world& world) { + flecs::module<ns::NestedModule>(world, "ns::NestedModule"); + flecs::component<Velocity>(world, "Velocity"); + } +}; + +class SimpleModule { +public: + SimpleModule(flecs::world& world) { + flecs::module<ns::SimpleModule>(world, "ns::SimpleModule"); + flecs::import<ns::NestedModule>(world); + flecs::component<Position>(world, "Position"); + } +}; + +class NestedTypeModule { +public: + struct NestedType { }; + + NestedTypeModule(flecs::world& world) { + world.module<NestedTypeModule>(); + world.component<NestedType>(); + world.component<NestedNameSpaceType>(); + } +}; + +class ModuleWithDifferentName { +public: + ModuleWithDifferentName(flecs::world& world) { + world.module<ModuleWithDifferentName>("ns::name_1"); + world.component<Position>("Position"); + } +}; + +class ModuleWithImport { +public: + ModuleWithImport(flecs::world& world) { + world.module<ModuleWithImport>("ns::name_2"); + world.import<ModuleWithDifferentName>(); + + // Ensure imported component can be used by systems + world.system<Position>() + .each([](flecs::entity e, Position& p) { }); + + world.system<Position>() + .iter([](flecs::iter e, Position *p) { }); + } +}; + +} + +void Module_import() { + flecs::world world; + auto m = flecs::import<ns::SimpleModule>(world); + test_assert(m.id() != 0); + test_str(m.path().c_str(), "::ns::SimpleModule"); + test_assert(m.has(flecs::Module)); + + auto e = flecs::entity(world) + .add<Position>(); + test_assert(e.id() != 0); + test_assert(e.has<Position>()); +} + +void Module_lookup_from_scope() { + flecs::world world; + flecs::import<ns::SimpleModule>(world); + + auto ns_entity = world.lookup("ns"); + test_assert(ns_entity.id() != 0); + + auto module_entity = world.lookup("ns::SimpleModule"); + test_assert(module_entity.id() != 0); + + auto position_entity = world.lookup("ns::SimpleModule::Position"); + test_assert(position_entity.id() != 0); + + auto nested_module = ns_entity.lookup("SimpleModule"); + test_assert(module_entity.id() == nested_module.id()); + + auto module_position = module_entity.lookup("Position"); + test_assert(position_entity.id() == module_position.id()); + + auto ns_position = ns_entity.lookup("SimpleModule::Position"); + test_assert(position_entity.id() == ns_position.id()); +} + +void Module_nested_module() { + flecs::world world; + flecs::import<ns::SimpleModule>(world); + + auto velocity = world.lookup("ns::NestedModule::Velocity"); + test_assert(velocity.id() != 0); + + test_str(velocity.path().c_str(), "::ns::NestedModule::Velocity"); +} + +void Module_nested_type_module() { + flecs::world world; + world.import<ns::NestedTypeModule>(); + + auto ns_entity = world.lookup("ns"); + test_assert(ns_entity.id() != 0); + + auto module_entity = world.lookup("ns::NestedTypeModule"); + test_assert(module_entity.id() != 0); + + auto type_entity = world.lookup("ns::NestedTypeModule::NestedType"); + test_assert(type_entity.id() != 0); + + auto ns_type_entity = world.lookup("ns::NestedTypeModule::NestedNameSpaceType"); + test_assert(ns_type_entity.id() != 0); + + int32_t childof_count = 0; + type_entity.each(flecs::ChildOf, [&](flecs::entity) { + childof_count ++; + }); + + test_int(childof_count, 1); + + childof_count = 0; + ns_type_entity.each(flecs::ChildOf, [&](flecs::entity) { + childof_count ++; + }); + + test_int(childof_count, 1); +} + +void Module_module_type_w_explicit_name() { + flecs::world world; + world.import<ns::ModuleWithImport>(); + + auto ns_entity = world.lookup("ns"); + test_assert(ns_entity.id() != 0); + + auto module_1 = world.lookup("ns::name_1"); + test_assert(module_1.id() != 0); + + auto module_2 = world.lookup("ns::name_2"); + test_assert(module_2.id() != 0); + + auto comp = world.lookup("ns::name_1::Position"); + test_assert(comp.id() != 0); +} + +void Module_component_redefinition_outside_module() { + flecs::world world; + + world.import<ns::SimpleModule>(); + + auto pos_comp = world.lookup("ns::SimpleModule::Position"); + test_assert(pos_comp.id() != 0); + + auto pos = world.component<Position>(); + test_assert(pos.id() != 0); + test_assert(pos.id() == pos_comp.id()); + + int32_t childof_count = 0; + pos_comp.each(flecs::ChildOf, [&](flecs::entity) { + childof_count ++; + }); + + test_int(childof_count, 1); +} + +void Module_module_tag_on_namespace() { + flecs::world world; + + auto mid = world.import<ns::NestedModule>(); + test_assert(mid.has(flecs::Module)); + + auto nsid = world.lookup("ns"); + test_assert(nsid.has(flecs::Module)); +} + +static int module_ctor_invoked = 0; +static int module_dtor_invoked = 0; + +class Module_w_dtor { +public: + Module_w_dtor(flecs::world& world) { + world.module<Module_w_dtor>(); + module_ctor_invoked ++; + + world.system<>().iter([](flecs::iter& it) { }); + } + + ~Module_w_dtor() { + module_dtor_invoked ++; + } +}; + +void Module_dtor_on_fini() { + { + flecs::world ecs; + + test_int(module_ctor_invoked, 0); + test_int(module_dtor_invoked, 0); + + ecs.import<Module_w_dtor>(); + + test_int(module_ctor_invoked, 1); + test_int(module_dtor_invoked, 0); + } + + test_int(module_dtor_invoked, 1); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Observer.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Observer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5a0c45734d5aeb03289e1bd74d46a22af837bb02 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Observer.cpp @@ -0,0 +1,228 @@ +#include <cpp_api.h> + +void Observer_2_terms_on_add() { + flecs::world ecs; + + int32_t count = 0; + + ecs.observer<Position, Velocity>() + .event(flecs::OnAdd) + .each([&](Position& p, Velocity& v) { + count ++; + }); + + auto e = ecs.entity(); + test_int(count, 0); + + e.set<Position>({10, 20}); + test_int(count, 0); + + e.set<Velocity>({1, 2}); + test_int(count, 1); +} + +void Observer_2_terms_on_remove() { + flecs::world ecs; + + int32_t count = 0; + + ecs.observer<Position, Velocity>() + .event(flecs::OnRemove) + .each([&](Position& p, Velocity& v) { + count ++; + test_int(p.x, 10); + test_int(p.y, 20); + test_int(v.x, 1); + test_int(v.y, 2); + }); + + auto e = ecs.entity(); + test_int(count, 0); + + e.set<Position>({10, 20}); + test_int(count, 0); + + e.set<Velocity>({1, 2}); + test_int(count, 0); + + e.remove<Velocity>(); + test_int(count, 1); + + e.remove<Position>(); + test_int(count, 1); +} + +void Observer_2_terms_on_set() { + flecs::world ecs; + + int32_t count = 0; + + ecs.observer<Position, Velocity>() + .event(flecs::OnSet) + .each([&](Position& p, Velocity& v) { + count ++; + test_int(p.x, 10); + test_int(p.y, 20); + test_int(v.x, 1); + test_int(v.y, 2); + }); + + auto e = ecs.entity(); + test_int(count, 0); + + e.set<Position>({10, 20}); + test_int(count, 0); + + e.set<Velocity>({1, 2}); + test_int(count, 1); +} + +void Observer_2_terms_un_set() { + flecs::world ecs; + + int32_t count = 0; + + ecs.observer<Position, Velocity>() + .event(flecs::UnSet) + .each([&](Position& p, Velocity& v) { + count ++; + test_int(p.x, 10); + test_int(p.y, 20); + test_int(v.x, 1); + test_int(v.y, 2); + }); + + auto e = ecs.entity(); + test_int(count, 0); + + e.set<Position>({10, 20}); + test_int(count, 0); + + e.set<Velocity>({1, 2}); + test_int(count, 0); + + e.remove<Velocity>(); + test_int(count, 1); + + e.remove<Position>(); + test_int(count, 1); +} + +void Observer_observer_w_self() { + flecs::world world; + + auto self = world.entity(); + + bool invoked = false; + world.observer<Position>() + .event(flecs::OnAdd) + .self(self) + .iter([&](flecs::iter& it) { + test_assert(it.self() == self); + invoked = true; + }); + + world.entity().set<Position>({10, 20}); + + test_bool(invoked, true); +} + +void Observer_10_terms() { + flecs::world ecs; + + int count = 0; + + auto e = ecs.entity(); + + ecs.observer<>() + .event(flecs::OnAdd) + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 10); + count ++; + }); + + e.add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>(); + + test_int(count, 1); +} + +void Observer_20_terms() { + flecs::world ecs; + + int count = 0; + + auto e = ecs.entity(); + + ecs.observer<>() + .event(flecs::OnAdd) + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .term<TagK>() + .term<TagL>() + .term<TagM>() + .term<TagN>() + .term<TagO>() + .term<TagP>() + .term<TagQ>() + .term<TagR>() + .term<TagS>() + .term<TagT>() + .iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 20); + count ++; + }); + + e.add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>() + .add<TagK>() + .add<TagL>() + .add<TagM>() + .add<TagN>() + .add<TagO>() + .add<TagP>() + .add<TagQ>() + .add<TagR>() + .add<TagS>() + .add<TagT>(); + + test_int(count, 1); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Pairs.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Pairs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f13b6e5ff45331249c55be488d5fe5149d4a805b --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Pairs.cpp @@ -0,0 +1,1042 @@ +#include <cpp_api.h> + +typedef struct Pair { + float value; +} Pair; + +void Pairs_add_component_pair() { + flecs::world ecs; + + auto entity = ecs.entity() + .add<Pair, Position>(); + + test_assert(entity.id() != 0); + test_assert((entity.has<Pair, Position>())); + test_assert((!entity.has<Position, Pair>())); + + test_str(entity.type().str().c_str(), "(Pair,Position)"); +} + +void Pairs_add_tag_pair() { + flecs::world ecs; + + ecs.component<Position>(); + auto Pair = ecs.entity("Pair"); + + auto entity = ecs.entity() + .add_w_object<Position>(Pair); + + test_assert(entity.id() != 0); + test_assert(entity.has_w_object<Position>(Pair)); + test_assert(!entity.has<Position>(Pair)); + test_str(entity.type().str().c_str(), "(Pair,Position)"); +} + +void Pairs_add_tag_pair_to_tag() { + flecs::world ecs; + + auto Tag = ecs.entity("Tag"); + auto Pair = ecs.entity("Pair"); + + auto entity = ecs.entity() + .add(Pair, Tag); + + test_assert(entity.id() != 0); + test_assert(entity.has(Pair, Tag)); + test_str(entity.type().str().c_str(), "(Pair,Tag)"); +} + +void Pairs_remove_component_pair() { + flecs::world ecs; + + ecs.component<Position>(); + ecs.component<Pair>(); + + auto entity = ecs.entity() + .add<Pair, Position>(); + + test_assert(entity.id() != 0); + test_assert((entity.has<Pair, Position>())); + test_assert((!entity.has<Position, Pair>())); + + test_str(entity.type().str().c_str(), "(Pair,Position)"); + + entity.remove<Position, Pair>(); + test_assert(!(entity.has<Position, Pair>())); +} + +void Pairs_remove_tag_pair() { + flecs::world ecs; + + ecs.component<Position>(); + auto Pair = ecs.entity("Pair"); + + auto entity = ecs.entity() + .add_w_object<Position>(Pair); + + test_assert(entity.id() != 0); + test_assert(entity.has_w_object<Position>(Pair)); + test_assert(!entity.has<Position>(Pair)); + test_str(entity.type().str().c_str(), "(Pair,Position)"); + + entity.remove<Position>(Pair); + test_assert(!entity.has<Position>(Pair)); +} + +void Pairs_remove_tag_pair_to_tag() { + flecs::world ecs; + + auto Tag = ecs.entity("Tag"); + auto Pair = ecs.entity("Pair"); + + auto entity = ecs.entity() + .add(Pair, Tag); + + test_assert(entity.id() != 0); + test_assert(entity.has(Pair, Tag)); + test_str(entity.type().str().c_str(), "(Pair,Tag)"); + + entity.remove(Tag, Pair); + test_assert(!entity.has(Tag, Pair)); +} + +void Pairs_set_component_pair() { + flecs::world ecs; + + auto entity = ecs.entity() + .set<Pair, Position>({10}); + + test_assert(entity.id() != 0); + test_assert((entity.has<Pair, Position>())); + test_assert((!entity.has<Position, Pair>())); + + test_str(entity.type().str().c_str(), "(Pair,Position)"); + + const Pair *t = entity.get<Pair, Position>(); + test_int(t->value, 10); +} + +void Pairs_set_tag_pair() { + flecs::world ecs; + + auto Pair = ecs.entity("Pair"); + + auto entity = ecs.entity() + .set_w_object<Position>(Pair, {10, 20}); + + test_assert(entity.id() != 0); + test_assert(entity.has_w_object<Position>(Pair)); + test_str(entity.type().str().c_str(), "(Pair,Position)"); + + const Position *p = entity.get_w_object<Position>(Pair); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Pairs_system_1_pair_instance() { + flecs::world ecs; + + ecs.entity() + .set<Pair, Position>({10}); + + int invoke_count = 0; + int entity_count = 0; + int trait_value = 0; + + ecs.system<>(nullptr, "(Pair, *)") + .iter([&](flecs::iter it) { + flecs::column<Pair> tr(it, 1); + invoke_count ++; + for (auto i : it) { + entity_count ++; + trait_value = tr[i].value; + } + }); + + ecs.progress(); + + test_int(invoke_count, 1); + test_int(entity_count, 1); + test_int(trait_value, 10); +} + +void Pairs_system_2_pair_instances() { + flecs::world ecs; + + ecs.entity() + .set<Pair, Position>({10}) + .set<Pair, Velocity>({20}); + + int invoke_count = 0; + int entity_count = 0; + int trait_value = 0; + + ecs.system<>(nullptr, "(Pair, *)") + .iter([&](flecs::iter it) { + flecs::column<Pair> tr(it, 1); + invoke_count ++; + for (auto i : it) { + entity_count ++; + trait_value += tr[i].value; + } + }); + + ecs.progress(); + + test_int(invoke_count, 2); + test_int(entity_count, 2); + test_int(trait_value, 30); +} + +void Pairs_override_pair() { + flecs::world ecs; + + auto base = ecs.entity() + .set<Pair, Position>({10}); + + auto instance = ecs.entity() + .add(flecs::IsA, base); + + test_assert((instance.has<Pair, Position>())); + const Pair *t = instance.get<Pair, Position>(); + test_int(t->value, 10); + + const Pair *t_2 = base.get<Pair, Position>(); + test_assert(t == t_2); + + instance.add<Pair, Position>(); + t = instance.get<Pair, Position>(); + test_int(t->value, 10); + test_assert(t != t_2); + + instance.remove<Pair, Position>(); + t = instance.get<Pair, Position>(); + test_int(t->value, 10); + test_assert(t == t_2); +} + +void Pairs_override_tag_pair() { + flecs::world ecs; + + auto Pair = ecs.entity(); + + auto base = ecs.entity() + .set_w_object<Position>(Pair, {10, 20}); + + auto instance = ecs.entity() + .add(flecs::IsA, base); + + test_assert((instance.has_w_object<Position>(Pair))); + const Position *t = instance.get_w_object<Position>(Pair); + test_int(t->x, 10); + test_int(t->y, 20); + + const Position *t_2 = base.get_w_object<Position>(Pair); + test_assert(t == t_2); + + instance.add_w_object<Position>(Pair); + t = instance.get_w_object<Position>(Pair); + test_int(t->x, 10); + test_int(t->y, 20); + test_assert(t != t_2); + + instance.remove_w_object<Position>(Pair); + t = instance.get_w_object<Position>(Pair); + test_int(t->x, 10); + test_int(t->y, 20); + test_assert(t == t_2); +} + +void Pairs_get_mut_pair() { + flecs::world ecs; + + auto e = ecs.entity(); + + bool added = false; + Pair *t = e.get_mut<Pair, Position>(&added); + test_assert(t != NULL); + test_bool(added, true); + t->value = 10; + + const Pair *t_2 = e.get<Pair, Position>(); + test_assert(t == t_2); + test_int(t->value, 10); +} + +void Pairs_get_mut_pair_existing() { + flecs::world ecs; + + auto e = ecs.entity() + .set<Pair, Position>({20}); + + bool added = false; + Pair *t = e.get_mut<Pair, Position>(&added); + test_assert(t != NULL); + test_bool(added, false); + test_int(t->value, 20); + t->value = 10; + + const Pair *t_2 = e.get<Pair, Position>(); + test_assert(t == t_2); + test_int(t->value, 10); +} + +void Pairs_get_mut_pair_tag() { + flecs::world ecs; + + auto Pair = ecs.entity(); + + auto e = ecs.entity(); + + bool added = false; + Position *p = e.get_mut_w_object<Position>(Pair, &added); + test_assert(p != NULL); + test_bool(added, true); + p->x = 10; + p->y = 20; + + const Position *p_2 = e.get_w_object<Position>(Pair); + test_assert(p == p_2); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Pairs_get_mut_pair_tag_existing() { + flecs::world ecs; + + auto Pair = ecs.entity(); + + auto e = ecs.entity() + .set_w_object<Position>(Pair, {10, 20}); + + bool added = false; + Position *p = e.get_mut_w_object<Position>(Pair, &added); + test_assert(p != NULL); + test_bool(added, false); + test_int(p->x, 10); + test_int(p->y, 20); + + const Position *p_2 = e.get_w_object<Position>(Pair); + test_assert(p == p_2); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Pairs_type_w_pair() { + flecs::world ecs; + + auto Type = ecs.type() + .add<Pair, Position>(); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has<Pair, Position>())); +} + +void Pairs_type_w_pair_tag() { + flecs::world ecs; + + auto Tag = ecs.entity(); + + auto Type = ecs.type() + .add<Pair>(Tag); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has<Pair>(Tag))); +} + +void Pairs_type_w_pair_tags() { + flecs::world ecs; + + auto Tag = ecs.entity(); + auto Pair = ecs.entity(); + + auto Type = ecs.type() + .add(Pair, Tag); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has(Pair, Tag))); +} + +void Pairs_type_w_tag_pair() { + flecs::world ecs; + + auto Tag = ecs.entity(); + + auto Type = ecs.type() + .add<Pair>(Tag); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has<Pair>(Tag))); +} + +void Pairs_override_pair_w_type() { + flecs::world ecs; + + auto Prefab = ecs.prefab("Prefab") + .set<Pair, Position>({10}); + + auto Type = ecs.type() + .add(flecs::IsA, Prefab) + .add<Pair, Position>(); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has<Pair, Position>())); + + const Pair *t_1 = Prefab.get<Pair, Position>(); + test_assert(t_1 != nullptr); + test_int(t_1->value, 10); + + const Pair *t_2 = e.get<Pair, Position>(); + test_assert(t_2 != nullptr); + + test_assert(t_1 != t_2); + test_int(t_2->value, 10); +} + +void Pairs_override_pair_w_type_tag() { + flecs::world ecs; + + auto Tag = ecs.entity(); + + auto Prefab = ecs.prefab("Prefab") + .set<Pair>(Tag, {10}); + + auto Type = ecs.type() + .add(flecs::IsA, Prefab) + .add<Pair>(Tag); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has<Pair>(Tag))); + + const Pair *t_1 = Prefab.get<Pair>(Tag); + test_assert(t_1 != nullptr); + test_int(t_1->value, 10); + + const Pair *t_2 = e.get<Pair>(Tag); + test_assert(t_2 != nullptr); + + test_assert(t_1 != t_2); + test_int(t_2->value, 10); +} + +void Pairs_override_tag_pair_w_type() { + flecs::world ecs; + + auto Pair = ecs.entity(); + + auto Prefab = ecs.prefab("Prefab") + .set_w_object<Position>(Pair, {10, 20}); + + auto Type = ecs.type() + .add(flecs::IsA, Prefab) + .add_w_object<Position>(Pair); + + auto e = ecs.entity() + .add(Type); + + test_assert((e.has_w_object<Position>(Pair))); + + const Position *p_1 = Prefab.get_w_object<Position>(Pair); + test_assert(p_1 != nullptr); + test_int(p_1->x, 10); + test_int(p_1->y, 20); + + const Position *p_2 = e.get_w_object<Position>(Pair); + test_assert(p_2 != nullptr); + + test_assert(p_1 != p_2); + test_int(p_2->x, 10); + test_int(p_2->y, 20); +} + +void Pairs_get_relation_from_id() { + flecs::world ecs; + + auto rel = ecs.entity(); + auto obj = ecs.entity(); + + flecs::id pair(rel, obj); + + test_assert(pair.relation() == rel); + test_assert(pair.object() != rel); + + test_assert(pair.relation().is_alive()); + test_assert(pair.relation().is_valid()); +} + +void Pairs_get_w_object_from_id() { + flecs::world ecs; + + auto rel = ecs.entity(); + auto obj = ecs.entity(); + + flecs::id pair(rel, obj); + + test_assert(pair.relation() != obj); + test_assert(pair.object() == obj); + + test_assert(pair.object().is_alive()); + test_assert(pair.object().is_valid()); +} + +void Pairs_get_recycled_relation_from_id() { + flecs::world ecs; + + auto rel = ecs.entity(); + auto obj = ecs.entity(); + + rel.destruct(); + obj.destruct(); + + rel = ecs.entity(); + obj = ecs.entity(); + + // Make sure ids are recycled + test_assert((uint32_t)rel.id() != rel.id()); + test_assert((uint32_t)obj.id() != obj.id()); + + flecs::id pair(rel, obj); + + test_assert(pair.relation() == rel); + test_assert(pair.object() != rel); + + test_assert(pair.relation().is_alive()); + test_assert(pair.relation().is_valid()); +} + +void Pairs_get_recycled_object_from_id() { + flecs::world ecs; + + auto rel = ecs.entity(); + auto obj = ecs.entity(); + + rel.destruct(); + obj.destruct(); + + rel = ecs.entity(); + obj = ecs.entity(); + + // Make sure ids are recycled + test_assert((uint32_t)rel.id() != rel.id()); + test_assert((uint32_t)obj.id() != obj.id()); + + flecs::id pair(rel, obj); + + test_assert(pair.relation() == rel); + test_assert(pair.object() != rel); + + test_assert(pair.object().is_alive()); + test_assert(pair.object().is_valid()); +} + +void Pairs_each() { + flecs::world ecs; + + auto p_1 = ecs.entity(); + auto p_2 = ecs.entity(); + + auto e = ecs.entity() + .add(p_1) + .add(p_2); + + int32_t count = 0; + + e.each([&](flecs::id e) { + if (count == 0) { + test_assert(e == p_1); + } else if (count == 1) { + test_assert(e == p_2); + } else { + test_assert(false); + } + + count ++; + }); + + test_int(count, 2); +} + +void Pairs_each_pair() { + flecs::world ecs; + + auto pair = ecs.component<Pair>(); + auto pos = ecs.component<Position>(); + auto vel = ecs.component<Velocity>(); + + auto e = ecs.entity() + .add<Pair, Position>() + .add<Pair, Velocity>(); + + int32_t count = 0; + + e.each(pair, [&](flecs::entity object) { + if (count == 0) { + test_assert(object == pos); + } else if (count == 1) { + test_assert(object == vel); + } else { + test_assert(false); + } + + count ++; + }); + + test_int(count, 2); +} + +void Pairs_each_pair_by_type() { + flecs::world ecs; + + auto pos = ecs.component<Position>(); + auto vel = ecs.component<Velocity>(); + + auto e = ecs.entity() + .add<Pair, Position>() + .add<Pair, Velocity>(); + + int32_t count = 0; + + e.each<Pair>([&](flecs::entity object) { + if (count == 0) { + test_assert(object == pos); + } else if (count == 1) { + test_assert(object == vel); + } else { + test_assert(false); + } + + count ++; + }); + + test_int(count, 2); +} + +void Pairs_each_pair_w_childof() { + flecs::world ecs; + + auto p_1 = ecs.entity(); + auto p_2 = ecs.entity(); + + auto e = ecs.entity() + .child_of(p_1) + .child_of(p_2); + + int32_t count = 0; + + e.each(flecs::ChildOf, [&](flecs::entity object) { + if (count == 0) { + test_assert(object == p_1); + } else if (count == 1) { + test_assert(object == p_2); + } else { + test_assert(false); + } + + count ++; + }); + + test_int(count, 2); +} + +void Pairs_each_pair_w_recycled_rel() { + flecs::world ecs; + + auto e_1 = ecs.entity(); + auto e_2 = ecs.entity(); + + ecs.entity().destruct(); // force recycling + + auto pair = ecs.entity(); + + test_assert((uint32_t)pair.id() != pair.id()); // ensure recycled + + auto e = ecs.entity() + .add(pair, e_1) + .add(pair, e_2); + + int32_t count = 0; + + // should work correctly + e.each(pair, [&](flecs::entity object) { + if (count == 0) { + test_assert(object == e_1); + } else if (count == 1) { + test_assert(object == e_2); + } else { + test_assert(false); + } + + count ++; + }); + + test_int(count, 2); +} + +void Pairs_each_pair_w_recycled_obj() { + flecs::world ecs; + + auto pair = ecs.component<Pair>(); + + ecs.entity().destruct(); // force recycling + auto e_1 = ecs.entity(); + test_assert((uint32_t)e_1.id() != e_1.id()); // ensure recycled + + ecs.entity().destruct(); + auto e_2 = ecs.entity(); + test_assert((uint32_t)e_2.id() != e_2.id()); + + auto e = ecs.entity() + .add<Pair>(e_1) + .add<Pair>(e_2); + + int32_t count = 0; + + // should work correctly + e.each(pair, [&](flecs::entity object) { + if (count == 0) { + test_assert(object == e_1); + } else if (count == 1) { + test_assert(object == e_2); + } else { + test_assert(false); + } + + count ++; + }); + + test_int(count, 2); +} + +void Pairs_match_pair() { + flecs::world ecs; + + auto Eats = ecs.entity(); + auto Dislikes = ecs.entity(); + + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + auto Bananas = ecs.entity(); + + auto e = ecs.entity() + .set<Position>({10, 20}) // should not be matched + .add(Eats, Apples) + .add(Eats, Pears) + .add(Dislikes, Bananas); + + int32_t count = 0; + + e.match(ecs.pair(Eats, Apples), + [&](flecs::id id) { + test_assert(id.relation() == Eats); + test_assert(id.object() == Apples); + count ++; + }); + + test_int(count, 1); +} + +void Pairs_match_pair_obj_wildcard() { + flecs::world ecs; + + auto Eats = ecs.entity(); + auto Dislikes = ecs.entity(); + + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + auto Bananas = ecs.entity(); + + auto e = ecs.entity() + .set<Position>({10, 20}) // should not be matched + .add(Eats, Apples) + .add(Eats, Pears) + .add(Dislikes, Bananas); + + int32_t count = 0; + + e.match(ecs.pair(Eats, flecs::Wildcard), + [&](flecs::id id) { + test_assert(id.relation() == Eats); + test_assert(id.object() == Apples || id.object() == Pears); + count ++; + }); + + test_int(count, 2); +} + +void Pairs_match_pair_rel_wildcard() { + flecs::world ecs; + + auto Eats = ecs.entity(); + auto Dislikes = ecs.entity(); + + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + auto Bananas = ecs.entity(); + + auto e = ecs.entity() + .set<Position>({10, 20}) // should not be matched + .add(Eats, Apples) + .add(Eats, Pears) + .add(Dislikes, Bananas); + + int32_t count = 0; + + e.match(ecs.pair(flecs::Wildcard, Pears), + [&](flecs::id id) { + test_assert(id.relation() == Eats); + test_assert(id.object() == Pears); + count ++; + }); + + test_int(count, 1); +} + +void Pairs_match_pair_both_wildcard() { + flecs::world ecs; + + auto Eats = ecs.entity(); + auto Dislikes = ecs.entity(); + + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + auto Bananas = ecs.entity(); + + auto e = ecs.entity() + .set<Position>({10, 20}) // should not be matched + .add(Eats, Apples) + .add(Eats, Pears) + .add(Dislikes, Bananas); + + int32_t count = 0; + + e.match(ecs.pair(flecs::Wildcard, flecs::Wildcard), + [&](flecs::id id) { + count ++; + }); + + test_int(count, 3); +} + +void Pairs_has_tag_w_object() { + flecs::world ecs; + + struct Likes { }; + + auto Bob = ecs.entity(); + auto e = ecs.entity().add<Likes>(Bob); + test_assert(e.has<Likes>(Bob)); +} + +void Pairs_has_w_object_tag() { + flecs::world ecs; + + struct Bob { }; + + auto Likes = ecs.entity(); + auto e = ecs.entity().add_w_object<Bob>(Likes); + test_assert(e.has_w_object<Bob>(Likes)); +} + +struct Eats { int amount; }; +struct Apples { }; +struct Pears { }; + +using EatsApples = flecs::pair<Eats, Apples>; +using EatsPears = flecs::pair<Eats, Pears>; + +void Pairs_add_pair_type() { + flecs::world ecs; + + auto e = ecs.entity().add<EatsApples>(); + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<EatsApples>())); +} + +void Pairs_remove_pair_type() { + flecs::world ecs; + + auto e = ecs.entity().add<EatsApples>(); + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<EatsApples>())); + + e.remove<EatsApples>(); + test_assert(!(e.has<Eats, Apples>())); + test_assert(!(e.has<EatsApples>())); +} + +void Pairs_set_pair_type() { + flecs::world ecs; + + auto e = ecs.entity().set<EatsApples>({10}); + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<EatsApples>())); + + const Eats *ptr = e.get<EatsApples>(); + test_int(ptr->amount, 10); + + test_assert((ptr == e.get<Eats, Apples>())); +} + +void Pairs_has_pair_type() { + flecs::world ecs; + + auto e = ecs.entity().add<Eats, Apples>(); + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<EatsApples>())); +} + +void Pairs_get_1_pair_arg() { + flecs::world ecs; + + auto e = ecs.entity().set<EatsApples>({10}); + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<EatsApples>())); + + test_bool(e.get([](const EatsApples& a) { + test_int(a->amount, 10); + }), true); +} + +void Pairs_get_2_pair_arg() { + flecs::world ecs; + + auto e = ecs.entity() + .set<EatsApples>({10}) + .set<EatsPears>({20}); + + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<Eats, Pears>())); + test_assert((e.has<EatsApples>())); + test_assert((e.has<EatsPears>())); + + test_bool(e.get([](const EatsApples& a, const EatsPears& p) { + test_int(a->amount, 10); + test_int(p->amount, 20); + }), true); +} + +void Pairs_set_1_pair_arg() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](EatsApples&& a) { + a->amount = 10; + }); + + auto eats = e.get<EatsApples>(); + test_int(eats->amount, 10); +} + +void Pairs_set_2_pair_arg() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](EatsApples&& a, EatsPears&& p) { + a->amount = 10; + p->amount = 20; + }); + + auto eats = e.get<EatsApples>(); + test_int(eats->amount, 10); + + eats = e.get<EatsPears>(); + test_int(eats->amount, 20); +} + +void Pairs_get_inline_pair_type() { + flecs::world ecs; + + auto e = ecs.entity().set<EatsApples>({10}); + test_assert((e.has<Eats, Apples>())); + test_assert((e.has<EatsApples>())); + + test_bool(e.get([](const flecs::pair<Eats, Apples>& a) { + test_int(a->amount, 10); + }), true); +} + +void Pairs_set_inline_pair_type() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](flecs::pair<Eats, Apples>&& a) { + a->amount = 10; + }); + + auto eats = e.get<EatsApples>(); + test_int(eats->amount, 10); +} + +void Pairs_get_pair_type_object() { + flecs::world ecs; + + auto e = ecs.entity().set_w_object<Apples, Eats>({10}); + test_assert((e.has<Apples, Eats>())); + + test_bool(e.get([](const flecs::pair_object<Apples, Eats>& a) { + test_int(a->amount, 10); + }), true); +} + +void Pairs_set_pair_type_object() { + flecs::world ecs; + + auto e = ecs.entity() + .set([](flecs::pair_object<Apples, Eats>&& a) { + a->amount = 10; + }); + + auto eats = e.get_w_object<Apples, Eats>(); + test_int(eats->amount, 10); +} + +struct Event { + const char *value; +}; + +struct Begin { }; +struct End { }; + +using BeginEvent = flecs::pair<Begin, Event>; +using EndEvent = flecs::pair<End, Event>; + +void Pairs_set_get_w_object_variants() { + flecs::world ecs; + + auto e1 = ecs.entity().set_w_object<Begin, Event>({"Big Bang"}); + test_assert((e1.has<Begin, Event>())); + const Event* v = e1.get_w_object<Begin, Event>(); + test_assert(v != NULL); + test_str(v->value, "Big Bang"); + + auto e2 = ecs.entity().set<Begin, Event>({"Big Bang"}); + test_assert((e2.has<Begin, Event>())); + v = e2.get<Begin, Event>(); + test_assert(v != NULL); + test_str(v->value, "Big Bang"); + + auto e3 = ecs.entity().set<flecs::pair<Begin, Event>>({"Big Bang"}); + test_assert((e3.has<flecs::pair<Begin, Event>>())); + v = e3.get<flecs::pair<Begin, Event>>(); + test_assert(v != NULL); + test_str(v->value, "Big Bang"); + + auto e4 = ecs.entity().set<BeginEvent>({"Big Bang"}); + test_assert((e4.has<BeginEvent>())); + v = e4.get<BeginEvent>(); + test_assert(v != NULL); + test_str(v->value, "Big Bang"); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Paths.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Paths.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a233f1ab3e3d19428fb219c6e8caf3a3377b5dfc --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Paths.cpp @@ -0,0 +1,181 @@ +#include <cpp_api.h> + +void Paths_name() { + flecs::world world; + + auto e = flecs::entity(world, "foo"); + test_str(e.name().c_str(), "foo"); + + auto e_world = world.lookup("foo"); + test_assert(e.id() == e_world.id()); + + e_world = world.lookup("::foo"); + test_assert(e.id() == e_world.id()); +} + +void Paths_path_depth_1() { + flecs::world world; + + auto e = flecs::entity(world, "foo::bar"); + test_str(e.name().c_str(), "bar"); + test_str(e.path().c_str(), "::foo::bar"); + + auto e_world = world.lookup("bar"); + test_assert(0 == e_world.id()); + + e_world = world.lookup("foo::bar"); + test_assert(e.id() == e_world.id()); + + e_world = world.lookup("::foo::bar"); + test_assert(e.id() == e_world.id()); +} + +void Paths_path_depth_2() { + flecs::world world; + + auto e = flecs::entity(world, "foo::bar::hello"); + test_str(e.name().c_str(), "hello"); + test_str(e.path().c_str(), "::foo::bar::hello"); + + auto e_world = world.lookup("hello"); + test_assert(0 == e_world.id()); + + e_world = world.lookup("foo::bar::hello"); + test_assert(e.id() == e_world.id()); + + e_world = world.lookup("::foo::bar::hello"); + test_assert(e.id() == e_world.id()); +} + +void Paths_entity_lookup_name() { + flecs::world world; + + auto parent = flecs::entity(world, "foo"); + test_str(parent.name().c_str(), "foo"); + test_str(parent.path().c_str(), "::foo"); + + auto e = flecs::entity(world, "foo::bar"); + test_str(e.name().c_str(), "bar"); + test_str(e.path().c_str(), "::foo::bar"); + + auto parent_e = parent.lookup("bar"); + test_assert(e.id() == parent_e.id()); + + parent_e = parent.lookup("::foo::bar"); + test_assert(e.id() == parent_e.id()); +} + +void Paths_entity_lookup_depth_1() { + flecs::world world; + + auto parent = flecs::entity(world, "foo"); + test_str(parent.name().c_str(), "foo"); + test_str(parent.path().c_str(), "::foo"); + + auto e = flecs::entity(world, "foo::bar::hello"); + test_str(e.name().c_str(), "hello"); + test_str(e.path().c_str(), "::foo::bar::hello"); + + auto parent_e = parent.lookup("bar::hello"); + test_assert(e.id() == parent_e.id()); + + parent_e = parent.lookup("::foo::bar::hello"); + test_assert(e.id() == parent_e.id()); +} + +void Paths_entity_lookup_depth_2() { + flecs::world world; + + auto parent = flecs::entity(world, "foo"); + test_str(parent.name().c_str(), "foo"); + test_str(parent.path().c_str(), "::foo"); + + auto e = flecs::entity(world, "foo::bar::hello::world"); + test_str(e.name().c_str(), "world"); + test_str(e.path().c_str(), "::foo::bar::hello::world"); + + auto parent_e = parent.lookup("bar::hello::world"); + test_assert(e.id() == parent_e.id()); + + parent_e = parent.lookup("::foo::bar::hello::world"); + test_assert(e.id() == parent_e.id()); +} + +void Paths_alias_component() { + flecs::world ecs; + + auto e = ecs.use<Position>("MyPosition"); + auto a = ecs.lookup("MyPosition"); + auto c = ecs.lookup("Position"); + + test_assert(e.id() == a.id()); + test_assert(e.id() == c.id()); +} + +namespace test { + struct Foo { + float x; + float y; + }; +} + +void Paths_alias_scoped_component() { + flecs::world ecs; + + auto e = ecs.use<test::Foo>(); + auto a = ecs.lookup("Foo"); + auto c = ecs.lookup("test::Foo"); + + test_assert(e.id() == a.id()); + test_assert(e.id() == c.id()); +} + +void Paths_alias_scoped_component_w_name() { + flecs::world ecs; + + auto e = ecs.use<test::Foo>("FooAlias"); + auto a = ecs.lookup("FooAlias"); + auto f = ecs.lookup("Foo"); + auto c = ecs.lookup("test::Foo"); + + test_assert(e.id() == a.id()); + test_assert(e.id() == c.id()); + test_assert(f.id() == 0); +} + +void Paths_alias_entity() { + flecs::world ecs; + + auto e = ecs.entity("Foo"); + + ecs.use(e, "FooAlias"); + + auto a = ecs.lookup("FooAlias"); + + test_assert(e.id() == a.id()); +} + +void Paths_alias_entity_by_name() { + flecs::world ecs; + + auto e = ecs.entity("Foo"); + + ecs.use(e, "FooAlias"); + + auto l = ecs.lookup("FooAlias"); + + test_assert(e.id() == l.id()); +} + +void Paths_alias_entity_by_scoped_name() { + flecs::world ecs; + + auto e = ecs.entity("Foo::Bar"); + + auto a = ecs.use("Foo::Bar", "FooAlias"); + + auto l = ecs.lookup("FooAlias"); + + test_assert(e.id() == a.id()); + test_assert(e.id() == l.id()); +} \ No newline at end of file diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Query.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Query.cpp new file mode 100644 index 0000000000000000000000000000000000000000..580f739f417e9b7a41040dab6cc4751164f610b6 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Query.cpp @@ -0,0 +1,1167 @@ +#include <cpp_api.h> + +struct Pair { + float value; +}; + +void Query_action() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto entity = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + flecs::query<Position, Velocity> q(world); + + q.iter([](flecs::iter& it, Position *p, Velocity *v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void Query_action_const() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto entity = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + flecs::query<Position, const Velocity> q(world); + + q.iter([](flecs::iter& it, Position *p, const Velocity *v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void Query_action_shared() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto base = flecs::entity(world) + .set<Velocity>({1, 2}); + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .add(flecs::IsA, base); + + auto e2 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({3, 4}); + + flecs::query<Position> q(world, "ANY:Velocity"); + + q.iter([](flecs::iter&it, Position *p) { + auto v = it.term<const Velocity>(2); + + if (v.is_shared()) { + for (auto i : it) { + p[i].x += v->x; + p[i].y += v->y; + } + } else { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + } + }); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 13); + test_int(p->y, 24); +} + +void Query_action_optional() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + flecs::component<Mass>(world, "Mass"); + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .set<Mass>({1}); + + auto e2 = flecs::entity(world) + .set<Position>({30, 40}) + .set<Velocity>({3, 4}) + .set<Mass>({1}); + + auto e3 = flecs::entity(world) + .set<Position>({50, 60}); + + auto e4 = flecs::entity(world) + .set<Position>({70, 80}); + + flecs::query<Position, Velocity*, Mass*> q(world); + + q.iter([](flecs::iter& it, Position *p, Velocity *v, Mass *m) { + if (it.is_set(2) && it.is_set(3)) { + for (auto i : it) { + p[i].x += v[i].x * m[i].value; + p[i].y += v[i].y * m[i].value; + } + } else { + for (auto i : it) { + p[i].x ++; + p[i].y ++; + } + } + }); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 33); + test_int(p->y, 44); + + p = e3.get<Position>(); + test_int(p->x, 51); + test_int(p->y, 61); + + p = e4.get<Position>(); + test_int(p->x, 71); + test_int(p->y, 81); +} + +void Query_each() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto entity = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + flecs::query<Position, Velocity> q(world); + + q.each([](flecs::entity e, Position& p, Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void Query_each_const() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto entity = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + flecs::query<Position, const Velocity> q(world); + + q.each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void Query_each_shared() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto base = flecs::entity(world) + .set<Velocity>({1, 2}); + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .add(flecs::IsA, base); + + auto e2 = flecs::entity(world) + .set<Position>({20, 30}) + .add(flecs::IsA, base); + + auto e3 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({3, 4}); + + flecs::query<Position, const Velocity> q(world); + + q.each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 21); + test_int(p->y, 32); + + p = e3.get<Position>(); + test_int(p->x, 13); + test_int(p->y, 24); +} + +void Query_each_optional() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + flecs::component<Mass>(world, "Mass"); + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .set<Mass>({1}); + + auto e2 = flecs::entity(world) + .set<Position>({30, 40}) + .set<Velocity>({3, 4}) + .set<Mass>({1}); + + auto e3 = flecs::entity(world) + .set<Position>({50, 60}); + + auto e4 = flecs::entity(world) + .set<Position>({70, 80}); + + flecs::query<Position, Velocity*, Mass*> q(world); + + q.each([](flecs::entity e, Position& p, Velocity* v, Mass *m) { + if (v && m) { + p.x += v->x * m->value; + p.y += v->y * m->value; + } else { + p.x ++; + p.y ++; + } + }); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 33); + test_int(p->y, 44); + + p = e3.get<Position>(); + test_int(p->x, 51); + test_int(p->y, 61); + + p = e4.get<Position>(); + test_int(p->x, 71); + test_int(p->y, 81); +} + +void Query_signature() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto entity = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + flecs::query<> q(world, "Position, Velocity"); + + q.iter([](flecs::iter& it) { + auto p = it.term<Position>(1); + auto v = it.term<Velocity>(2); + + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void Query_signature_const() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto entity = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + flecs::query<> q(world, "Position, [in] Velocity"); + + q.iter([](flecs::iter& it) { + auto p = it.term<Position>(1); + auto v = it.term<const Velocity>(2); + + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void Query_signature_shared() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto base = flecs::entity(world) + .set<Velocity>({1, 2}); + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .add(flecs::IsA, base); + + auto e2 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({3, 4}); + + flecs::query<> q(world, "Position, [in] ANY:Velocity"); + + q.iter([](flecs::iter&it) { + auto p = it.term<Position>(1); + auto v = it.term<const Velocity>(2); + + if (v.is_shared()) { + for (auto i : it) { + p[i].x += v->x; + p[i].y += v->y; + } + } else { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + } + }); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 13); + test_int(p->y, 24); +} + +void Query_signature_optional() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + flecs::component<Mass>(world, "Mass"); + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .set<Mass>({1}); + + auto e2 = flecs::entity(world) + .set<Position>({30, 40}) + .set<Velocity>({3, 4}) + .set<Mass>({1}); + + auto e3 = flecs::entity(world) + .set<Position>({50, 60}); + + auto e4 = flecs::entity(world) + .set<Position>({70, 80}); + + flecs::query<> q(world, "Position, ?Velocity, ?Mass"); + + q.iter([](flecs::iter& it) { + auto p = it.term<Position>(1); + auto v = it.term<const Velocity>(2); + auto m = it.term<const Mass>(3); + + if (v.is_set() && m.is_set()) { + for (auto i : it) { + p[i].x += v[i].x * m[i].value; + p[i].y += v[i].y * m[i].value; + } + } else { + for (auto i : it) { + p[i].x ++; + p[i].y ++; + } + } + }); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 33); + test_int(p->y, 44); + + p = e3.get<Position>(); + test_int(p->x, 51); + test_int(p->y, 61); + + p = e4.get<Position>(); + test_int(p->x, 71); + test_int(p->y, 81); +} + +void Query_subquery() { + flecs::world world; + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + auto e2 = flecs::entity(world) + .set<Velocity>({1, 2}); + + flecs::query<Position> q(world); + flecs::query<Velocity> sq(world, q); + + sq.each([](flecs::entity e, Velocity& v) { + v.x ++; + v.y ++; + }); + + const Velocity *v = e1.get<Velocity>(); + test_int(v->x, 2); + test_int(v->y, 3); + + v = e2.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void Query_subquery_w_expr() { + flecs::world world; + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + auto e2 = flecs::entity(world) + .set<Velocity>({1, 2}); + + flecs::query<Position> q(world); + flecs::query<> sq(world, q, "Velocity"); + + sq.iter([](flecs::iter it) { + auto v = it.term<Velocity>(1); + + for (auto i : it) { + v[i].x ++; + v[i].y ++; + } + }); + + const Velocity *v = e1.get<Velocity>(); + test_int(v->x, 2); + test_int(v->y, 3); + + v = e2.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void Query_query_single_pair() { + flecs::world world; + + flecs::entity(world).add<Pair, Position>(); + auto e2 = flecs::entity(world).add<Pair, Velocity>(); + + flecs::query<> q(world, "(Pair, Velocity)"); + + int32_t table_count = 0; + int32_t entity_count = 0; + + q.iter([&](flecs::iter it) { + table_count ++; + for (auto i : it) { + test_assert(it.entity(i) == e2); + entity_count ++; + } + }); + + test_int(table_count, 1); + test_int(entity_count, 1); +} + +void Query_tag_w_each() { + flecs::world world; + + auto q = world.query<Tag>(); + + auto e = world.entity() + .add<Tag>(); + + q.each([&](flecs::entity qe, Tag) { + test_assert(qe == e); + }); +} + +void Query_shared_tag_w_each() { + flecs::world world; + + auto q = world.query<Tag>(); + + auto base = world.prefab() + .add<Tag>(); + + auto e = world.entity() + .add(flecs::IsA, base); + + q.each([&](flecs::entity qe, Tag) { + test_assert(qe == e); + }); +} + +static +int compare_position( + flecs::entity_t e1, + const Position *p1, + flecs::entity_t e2, + const Position *p2) +{ + return (p1->x > p2->x) - (p1->x < p2->x); +} + +void Query_sort_by() { + flecs::world world; + + world.entity().set<Position>({1, 0}); + world.entity().set<Position>({6, 0}); + world.entity().set<Position>({2, 0}); + world.entity().set<Position>({5, 0}); + world.entity().set<Position>({4, 0}); + + auto q = world.query<Position>(); + + q.order_by(compare_position); + + q.iter([](flecs::iter it, Position *p) { + test_int(it.count(), 5); + test_int(p[0].x, 1); + test_int(p[1].x, 2); + test_int(p[2].x, 4); + test_int(p[3].x, 5); + test_int(p[4].x, 6); + }); +} + +void Query_changed() { + flecs::world world; + + auto e = world.entity().set<Position>({1, 0}); + + auto q = world.query<Position>(); + + test_bool(q.changed(), true); + + q.each([](flecs::entity e, Position& p) { }); + + test_bool(q.changed(), false); + + e.set<Position>({2, 0}); + + test_bool(q.changed(), true); +} + +void Query_orphaned() { + flecs::world world; + + auto q = flecs::query<Position>(world); + + auto sq = world.query<Position>(q); + + test_assert(!q.orphaned()); + test_assert(!sq.orphaned()); + + q.destruct(); + + test_assert(sq.orphaned()); +} + +void Query_default_ctor() { + flecs::world world; + + flecs::query<Position> q_var; + + int count = 0; + auto q = world.query<Position>(); + + world.entity().set<Position>({10, 20}); + + q_var = q; + + q_var.each([&](flecs::entity e, Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + count ++; + }); + + test_int(count, 1); +} + +void Query_expr_w_template() { + flecs::world world; + + auto comp = world.component<Template<int>>(); + test_str(comp.name(), "Template<int>"); + + int count = 0; + auto q = world.query<Position>("Template<int>"); + + world.entity() + .set<Position>({10, 20}) + .set<Template<int>>({30, 40}); + + q.each([&](flecs::entity e, Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + + const Template<int> *t = e.get<Template<int>>(); + test_int(t->x, 30); + test_int(t->y, 40); + + count ++; + }); + + test_int(count, 1); +} + +void Query_query_type_w_template() { + flecs::world world; + + auto comp = world.component<Template<int>>(); + test_str(comp.name(), "Template<int>"); + + int count = 0; + auto q = world.query<Position, Template<int>>(); + + world.entity() + .set<Position>({10, 20}) + .set<Template<int>>({30, 40}); + + q.each([&](flecs::entity e, Position& p, Template<int>& t) { + test_int(p.x, 10); + test_int(p.y, 20); + + test_int(t.x, 30); + test_int(t.y, 40); + + count ++; + }); + + test_int(count, 1); +} + +void Query_compare_term_id() { + flecs::world world; + + int count = 0; + auto e = world.entity().add<Tag>(); + + auto q = world.query_builder<>() + .term<Tag>() + .build(); + + q.iter([&](flecs::iter& it) { + test_assert(it.term_id(1) == it.world().id<Tag>()); + test_assert(it.entity(0) == e); + count ++; + }); + + test_int(count, 1); +} + +void Query_test_no_defer_each() { + install_test_abort(); + + flecs::world world; + + struct Value { int value; }; + + world.entity().add<Tag>().set<Value>({10}); + + auto q = world.query_builder<Value>().term<Tag>().build(); + + q.each([](flecs::entity e, Value& v) { + test_expect_abort(); + e.remove<Tag>(); + }); + + test_assert(false); // Should never get here +} + +void Query_test_no_defer_iter() { + install_test_abort(); + + flecs::world world; + + struct Value { int value; }; + + world.entity().add<Tag>().set<Value>({10}); + + auto q = world.query_builder<Value>().term<Tag>().build(); + + q.iter([](flecs::iter& it, Value *v) { + for (auto i : it) { + test_expect_abort(); + it.entity(i).remove<Tag>(); + } + }); + + test_assert(false); // Should never get here +} + +void Query_inspect_terms() { + flecs::world world; + + auto p = world.entity(); + + auto q = world.query_builder<Position>() + .term<Velocity>() + .term(flecs::ChildOf, p) + .build(); + + test_int(3, q.term_count()); + + auto t = q.term(0); + test_int(t.id(), world.id<Position>()); + test_int(t.oper(), flecs::And); + test_int(t.inout(), flecs::InOutDefault); + + t = q.term(1); + test_int(t.id(), world.id<Velocity>()); + test_int(t.oper(), flecs::And); + test_int(t.inout(), flecs::InOutDefault); + + t = q.term(2); + test_int(t.id(), world.pair(flecs::ChildOf, p)); + test_int(t.oper(), flecs::And); + test_int(t.inout(), flecs::InOutDefault); + test_assert(t.id().object() == p); +} + +void Query_inspect_terms_w_each() { + flecs::world world; + + auto p = world.entity(); + + auto q = world.query_builder<Position>() + .term<Velocity>() + .term(flecs::ChildOf, p) + .build(); + + int32_t count = 0; + q.each_term([&](flecs::term& t) { + if (count == 0) { + test_int(t.id(), world.id<Position>()); + } else if (count == 1) { + test_int(t.id(), world.id<Velocity>()); + } else if (count == 2) { + test_int(t.id(), world.pair(flecs::ChildOf, p)); + test_assert(t.id().object() == p); + } else { + test_assert(false); + } + + test_int(t.oper(), flecs::And); + test_int(t.inout(), flecs::InOutDefault); + + count ++; + }); + + test_int(count, 3); +} + + +void Query_comp_to_str() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>() + .build(); + test_str(q.str(), "Position, Velocity"); +} + +struct Eats { int amount; }; +struct Apples { }; +struct Pears { }; + +void Query_pair_to_str() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>() + .term<Eats, Apples>() + .build(); + test_str(q.str(), "Position, Velocity, (Eats, Apples)"); +} + +void Query_oper_not_to_str() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>().oper(flecs::Not) + .build(); + test_str(q.str(), "Position, !Velocity"); +} + +void Query_oper_optional_to_str() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>().oper(flecs::Optional) + .build(); + test_str(q.str(), "Position, ?Velocity"); +} + +void Query_oper_or_to_str() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term<Position>().oper(flecs::Or) + .term<Velocity>().oper(flecs::Or) + .build(); + test_str(q.str(), "Position || Velocity"); +} + +using EatsApples = flecs::pair<Eats, Apples>; +using EatsPears = flecs::pair<Eats, Pears>; + +void Query_each_pair_type() { + flecs::world ecs; + + auto e1 = ecs.entity() + .set<EatsApples>({10}); + + ecs.entity() + .set<EatsPears>({20}); + + auto q = ecs.query<EatsApples>(); + + int count = 0; + q.each([&](flecs::entity e, EatsApples&& a) { + test_int(a->amount, 10); + test_assert(e == e1); + a->amount ++; + count ++; + }); + + test_int(count, 1); + + auto v = e1.get<EatsApples>(); + test_assert(v != NULL); + test_int(v->amount, 11); +} + +void Query_iter_pair_type() { + flecs::world ecs; + + auto e1 = ecs.entity() + .set<EatsApples>({10}); + + ecs.entity() + .set<EatsPears>({20}); + + auto q = ecs.query<EatsApples>(); + + int count = 0; + q.iter([&](flecs::iter& it, Eats* a) { + test_int(it.count(), 1); + + test_int(a->amount, 10); + test_assert(it.entity(0) == e1); + + a->amount ++; + count ++; + }); + + test_int(count, 1); + + auto v = e1.get<EatsApples>(); + test_assert(v != NULL); + test_int(v->amount, 11); +} + +void Query_term_pair_type() { + flecs::world ecs; + + auto e1 = ecs.entity() + .set<EatsApples>({10}); + + ecs.entity() + .set<EatsPears>({20}); + + auto q = ecs.query_builder<>() + .term<EatsApples>() + .build(); + + int count = 0; + q.iter([&](flecs::iter& it) { + test_int(it.count(), 1); + + auto a = it.term<EatsApples>(1); + + test_int(a->amount, 10); + test_assert(it.entity(0) == e1); + + a->amount ++; + count ++; + }); + + test_int(count, 1); + + auto v = e1.get<EatsApples>(); + test_assert(v != NULL); + test_int(v->amount, 11); +} + +void Query_each_no_entity_1_comp() { + flecs::world ecs; + + auto e = ecs.entity() + .set(Position{1, 2}); + + auto q = ecs.query<Position>(); + + int32_t count = 0; + q.each([&](Position& p) { + test_int(p.x, 1); + test_int(p.y, 2); + p.x += 1; + p.y += 2; + count ++; + }); + + test_int(count, 1); + + auto pos = e.get<Position>(); + test_int(pos->x, 2); + test_int(pos->y, 4); +} + +void Query_each_no_entity_2_comps() { + flecs::world ecs; + + auto e = ecs.entity() + .set(Position{1, 2}) + .set(Velocity{10, 20}); + + auto q = ecs.query<Position, Velocity>(); + + int32_t count = 0; + q.each([&](Position& p, Velocity& v) { + test_int(p.x, 1); + test_int(p.y, 2); + test_int(v.x, 10); + test_int(v.y, 20); + + p.x += 1; + p.y += 2; + v.x += 1; + v.y += 2; + count ++; + }); + + test_int(count, 1); + + test_bool(e.get([](const Position& p, const Velocity& v) { + test_int(p.x, 2); + test_int(p.y, 4); + + test_int(v.x, 11); + test_int(v.y, 22); + }), true); + + test_int(count, 1); +} + +void Query_iter_no_comps_1_comp() { + flecs::world ecs; + + ecs.entity().add<Position>(); + ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + auto q = ecs.query<Position>(); + + int32_t count = 0; + q.iter([&](flecs::iter& it) { + count += it.count(); + }); + + test_int(count, 3); +} + +void Query_iter_no_comps_2_comps() { + flecs::world ecs; + + ecs.entity().add<Velocity>(); + ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>().add<Velocity>(); + + auto q = ecs.query<Position, Velocity>(); + + int32_t count = 0; + q.iter([&](flecs::iter& it) { + + count += it.count(); + }); + + test_int(count, 2); +} + +void Query_iter_no_comps_no_comps() { + flecs::world ecs; + + ecs.entity().add<Velocity>(); + ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>().add<Velocity>(); + + auto q = ecs.query_builder<>() + .term<Position>() + .build(); + + int32_t count = 0; + q.iter([&](flecs::iter& it) { + count += it.count(); + }); + + test_int(count, 3); +} + +#include <iostream> + +struct Event { + const char *value; +}; + +struct Begin { }; +struct End { }; + +using BeginEvent = flecs::pair<Begin, Event>; +using EndEvent = flecs::pair<End, Event>; + +void Query_each_pair_object() { + flecs::world ecs; + + auto e1 = ecs.entity() + .set_w_object<Begin, Event>({"Big Bang"}) + .set<EndEvent>({"Heat Death"}); + + auto q = ecs.query<BeginEvent, EndEvent>(); + + int32_t count = 0; + q.each([&](flecs::entity e, BeginEvent b_e, EndEvent e_e) { + test_assert(e == e1); + test_str(b_e->value, "Big Bang"); + test_str(e_e->value, "Heat Death"); + count ++; + }); + + test_int(count, 1); +} + +void Query_iter_pair_object() { + flecs::world ecs; + + auto e1 = ecs.entity() + .set_w_object<Begin, Event>({"Big Bang"}) + .set<EndEvent>({"Heat Death"}); + + auto q = ecs.query<BeginEvent, EndEvent>(); + + int32_t count = 0; + q.iter([&](flecs::iter it, Event *b_e, Event *e_e) { + for (auto i : it) { + test_assert(it.entity(i) == e1); + test_str(b_e[i].value, "Big Bang"); + test_str(e_e[i].value, "Heat Death"); + count ++; + } + }); + + test_int(count, 1); +} + +void Query_iter_query_in_system() { + flecs::world ecs; + + ecs.entity().add<Position>().add<Velocity>(); + + auto q = ecs.query<Velocity>(); + + int32_t count = 0; + ecs.system<Position>() + .each([&](flecs::entity e1, Position&) { + q.each([&](flecs::entity e2, Velocity&) { + count ++; + }); + }); + + ecs.progress(); + + test_int(count, 1); +} + +void Query_iter_type() { + flecs::world ecs; + + ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + + auto q = ecs.query<Position>(); + + q.iter([&](flecs::iter it) { + test_assert(it.type().vector().count() >= 1); + test_assert(it.type().has<Position>()); + }); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/QueryBuilder.cpp b/fggl/ecs2/flecs/test/cpp_api/src/QueryBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e54074580f917984b6f0de3e9aebeb27e91ec8d --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/QueryBuilder.cpp @@ -0,0 +1,1412 @@ +#include <cpp_api.h> + +struct Other { + int32_t value; +}; + +void QueryBuilder_builder_assign_same_type() { + flecs::world ecs; + + flecs::query<Position, Velocity> q = + ecs.query_builder<Position, Velocity>(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_builder_assign_to_empty() { + flecs::world ecs; + + flecs::query<> q = ecs.query_builder<Position, Velocity>(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_builder_assign_from_empty() { + flecs::world ecs; + + flecs::query<> q = ecs.query_builder<>() + .term<Position>() + .term<Velocity>(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_builder_build() { + flecs::world ecs; + + flecs::query<Position, Velocity> q = + ecs.query_builder<Position, Velocity>().build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_builder_build_to_auto() { + flecs::world ecs; + + auto q = ecs.query_builder<Position, Velocity>().build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_builder_build_n_statements() { + flecs::world ecs; + + auto qb = ecs.query_builder<>(); + qb.term<Position>(); + qb.term<Velocity>(); + auto q = qb.build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_1_type() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>().build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_1_type() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term<Position>() + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_2_types() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term<Position>() + .term<Velocity>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_1_type_w_1_type() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_2_types_w_1_type() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>() + .term<Mass>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>().add<Mass>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_pair() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.query_builder<>() + .term(Likes, Bob) + .build(); + + auto e1 = ecs.entity().add(Likes, Bob); + ecs.entity().add(Likes, Alice); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_not() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Velocity>().oper(flecs::Not) + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_add_or() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term<Position>().oper(flecs::Or) + .term<Velocity>().oper(flecs::Or) + .build(); + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Velocity>(); + ecs.entity().add<Mass>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 2); +} + +void QueryBuilder_add_optional() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term<Position>() + .term<Velocity>().oper(flecs::Optional) + .build(); + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>().add<Mass>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 2); +} + +void QueryBuilder_ptr_type() { + flecs::world ecs; + + auto q = ecs.query_builder<Position, Velocity*>().build(); + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>().add<Mass>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p, Velocity* v) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 2); +} + +void QueryBuilder_const_type() { + flecs::world ecs; + + auto q = ecs.query_builder<const Position>().build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, const Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_string_term() { + flecs::world ecs; + + ecs.component<Position>(); + + auto q = ecs.query_builder<>() + .term("Position") + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_singleton_term() { + flecs::world ecs; + + ecs.set<Other>({10}); + + auto q = ecs.query_builder<Self>() + .term<Other>().singleton() + .build(); + + auto + e = ecs.entity(); e.set<Self>({e}); + e = ecs.entity(); e.set<Self>({e}); + e = ecs.entity(); e.set<Self>({e}); + + int32_t count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + test_assert(!o.is_owned()); + test_int(o->value, 10); + + const Other& o_ref = *o; + test_int(o_ref.value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 3); +} + +void QueryBuilder_isa_superset_term() { + flecs::world ecs; + + auto q = ecs.query_builder<Self>() + .term<Other>().subject().set(flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + + int32_t count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + test_assert(!o.is_owned()); + test_int(o->value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 3); +} + +void QueryBuilder_isa_self_superset_term() { + flecs::world ecs; + + auto q = ecs.query_builder<Self>() + .term<Other>().subject().set(flecs::Self | flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + + int32_t count = 0; + int32_t owned_count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + + if (!o.is_owned()) { + test_int(o->value, 10); + } else { + for (auto i : it) { + test_int(o[i].value, 20); + owned_count ++; + } + } + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 5); + test_int(owned_count, 2); +} + +void QueryBuilder_childof_superset_term() { + flecs::world ecs; + + auto q = ecs.query_builder<Self>() + .term<Other>().subject().set(flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + + int32_t count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + test_assert(!o.is_owned()); + test_int(o->value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 3); +} + +void QueryBuilder_childof_self_superset_term() { + flecs::world ecs; + + auto q = ecs.query_builder<Self>() + .term<Other>().subject().set(flecs::Self | flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + e = ecs.entity().set<Other>({20}); e.set<Self>({e}); + + int32_t count = 0; + int32_t owned_count = 0; + + q.iter([&](flecs::iter& it, Self *s) { + auto o = it.term<const Other>(2); + + if (!o.is_owned()) { + test_int(o->value, 10); + } else { + for (auto i : it) { + test_int(o[i].value, 20); + owned_count ++; + } + } + + for (auto i : it) { + test_assert(it.entity(i) == s[i].value); + count ++; + } + }); + + test_int(count, 5); + test_int(owned_count, 2); +} + +void QueryBuilder_isa_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).subject().set(flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void QueryBuilder_isa_self_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).subject().set(flecs::Self | flecs::SuperSet) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void QueryBuilder_childof_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).subject().set(flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void QueryBuilder_childof_self_superset_term_w_each() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).subject().set(flecs::Self | flecs::SuperSet, flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void QueryBuilder_isa_superset_shortcut() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset() + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void QueryBuilder_isa_superset_shortcut_w_self() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset(flecs::IsA, flecs::Self) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().is_a(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void QueryBuilder_childof_superset_shortcut() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset(flecs::ChildOf) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 3); +} + +void QueryBuilder_childof_superset_shortcut_w_self() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset(flecs::ChildOf, flecs::Self) + .build(); + + auto base = ecs.entity().set<Other>({10}); + + auto + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().child_of(base); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + e = ecs.entity().set<Other>({10}); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 5); +} + +void QueryBuilder_isa_superset_max_depth_1() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset().max_depth(1) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + + e = ecs.entity().is_a(base_2); e.set<Self>({0}); + e = ecs.entity().is_a(base_2); e.set<Self>({0}); + + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 2); +} + +void QueryBuilder_isa_superset_max_depth_2() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset().max_depth(2) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + e = ecs.entity().is_a(base_1); e.set<Self>({e}); + + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + e = ecs.entity().is_a(base_3); e.set<Self>({0}); + + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 4); +} + +void QueryBuilder_isa_superset_min_depth_2() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset().min_depth(2) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + + e = ecs.entity().is_a(base_4); e.set<Self>({e}); + e = ecs.entity().is_a(base_4); e.set<Self>({e}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 6); +} + +void QueryBuilder_isa_superset_min_depth_2_max_depth_3() { + flecs::world ecs; + + auto q = ecs.query_builder<Self, Other>() + .arg(2).superset().min_depth(2).max_depth(3) + .build(); + + auto base_1 = ecs.entity().set<Other>({10}); + auto base_2 = ecs.entity().is_a(base_1); + auto base_3 = ecs.entity().is_a(base_2); + auto base_4 = ecs.entity().is_a(base_3); + + auto + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + e = ecs.entity().is_a(base_1); e.set<Self>({0}); + + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + e = ecs.entity().is_a(base_2); e.set<Self>({e}); + + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + e = ecs.entity().is_a(base_3); e.set<Self>({e}); + + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + e = ecs.entity().is_a(base_4); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s, Other& o) { + test_assert(e == s.value); + test_int(o.value, 10); + count ++; + }); + + test_int(count, 4); +} + +void QueryBuilder_role() { + flecs::world ecs; + + struct Walking { }; + struct Running { }; + + auto Movement = ecs.type() + .add<Walking>() + .add<Running>(); + + auto q = ecs.query_builder<Self>() + .term(Movement).role(flecs::Switch) + .term<Running>().role(flecs::Case) + .build(); + + auto + e = ecs.entity().add_switch(Movement).add_case<Running>(); e.set<Self>({e}); + e = ecs.entity().add_switch(Movement).add_case<Running>(); e.set<Self>({e}); + + e = ecs.entity().add_switch(Movement).add_case<Walking>(); e.set<Self>({0}); + e = ecs.entity().add_switch(Movement).add_case<Walking>(); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void QueryBuilder_relation() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.query_builder<Self>() + .term(Likes, Bob) + .build(); + + auto + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + + e = ecs.entity().add(Likes, Alice); e.set<Self>({0}); + e = ecs.entity().add(Likes, Alice); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void QueryBuilder_relation_w_object_wildcard() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.query_builder<Self>() + .term(Likes, flecs::Wildcard) + .build(); + + auto + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + e = ecs.entity().add(Likes, Bob); e.set<Self>({e}); + + e = ecs.entity().add(Likes, Alice); e.set<Self>({e}); + e = ecs.entity().add(Likes, Alice); e.set<Self>({e}); + + e = ecs.entity(); e.set<Self>({0}); + e = ecs.entity(); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 4); +} + +void QueryBuilder_relation_w_predicate_wildcard() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Dislikes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.query_builder<Self>() + .term(flecs::Wildcard, Alice) + .build(); + + auto + e = ecs.entity().add(Likes, Alice); e.set<Self>({e}); + e = ecs.entity().add(Dislikes, Alice); e.set<Self>({e}); + + e = ecs.entity().add(Likes, Bob); e.set<Self>({0}); + e = ecs.entity().add(Dislikes, Bob); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void QueryBuilder_add_pair_w_rel_type() { + flecs::world ecs; + + struct Likes { }; + + auto Dislikes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto q = ecs.query_builder<Self>() + .term<Likes>(flecs::Wildcard) + .build(); + + auto + e = ecs.entity().add<Likes>(Alice); e.set<Self>({e}); + e = ecs.entity().add(Dislikes, Alice); e.set<Self>({0}); + + e = ecs.entity().add<Likes>(Bob); e.set<Self>({e}); + e = ecs.entity().add(Dislikes, Bob); e.set<Self>({0}); + + int32_t count = 0; + + q.each([&](flecs::entity e, Self& s) { + test_assert(e == s.value); + count ++; + }); + + test_int(count, 2); +} + +void QueryBuilder_template_term() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Template<int>>() + .build(); + + auto e1 = ecs.entity().add<Position>().add<Template<int>>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_subject_w_id() { + flecs::world ecs; + + auto q = ecs.query_builder<Position>() + .term<Position>().entity(flecs::This) + .build(); + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_subject_w_type() { + flecs::world ecs; + + ecs.set<Position>({10, 20}); + + auto q = ecs.query_builder<Position>() + .term<Position>().subject<Position>() + .build(); + + int32_t count = 0; + q.each([&](flecs::entity e, Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + count ++; + test_assert(e == ecs.singleton<Position>()); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_object_w_id() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Alice = ecs.entity(); + auto Bob = ecs.entity(); + + auto q = ecs.query_builder<>() + .term(Likes).object(Alice) + .build(); + + auto e1 = ecs.entity().add(Likes, Alice); + ecs.entity().add(Likes, Bob); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_object_w_type() { + flecs::world ecs; + + auto Likes = ecs.entity(); + struct Alice { }; + auto Bob = ecs.entity(); + + auto q = ecs.query_builder<>() + .term(Likes).object<Alice>() + .build(); + + auto e1 = ecs.entity().add(Likes, ecs.id<Alice>()); + ecs.entity().add(Likes, Bob); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_term() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term(ecs.term().id<Position>()) + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_term_w_type() { + flecs::world ecs; + + auto q = ecs.query_builder<>() + .term(ecs.term<Position>()) + .build(); + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_term_w_pair_type() { + flecs::world ecs; + + struct Likes { }; + struct Alice { }; + struct Bob { }; + + auto q = ecs.query_builder<>() + .term(ecs.term<Likes, Alice>()) + .build(); + + auto e1 = ecs.entity().add<Likes, Alice>(); + ecs.entity().add<Likes, Bob>(); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_term_w_id() { + flecs::world ecs; + + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + + auto q = ecs.query_builder<>() + .term(ecs.term(Apples)) + .build(); + + auto e1 = ecs.entity().add(Apples); + ecs.entity().add(Pears); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_explicit_term_w_pair_id() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Apples = ecs.entity(); + auto Pears = ecs.entity(); + + auto q = ecs.query_builder<>() + .term(ecs.term(Likes, Apples)) + .build(); + + auto e1 = ecs.entity().add(Likes, Apples); + ecs.entity().add(Likes, Pears); + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 1); +} + +void QueryBuilder_1_term_to_empty() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Apples = ecs.entity(); + + auto qb = ecs.query_builder<>() + .term<Position>(); + + qb.term(Likes, Apples); + + auto q = qb.build(); + + test_int(q.term_count(), 2); + test_int(q.term(0).id(), ecs.id<Position>()); + test_int(q.term(1).id(), ecs.pair(Likes, Apples)); +} + +void QueryBuilder_2_subsequent_args() { + flecs::world ecs; + + struct Rel { int foo; }; + + int32_t count = 0; + + auto s = ecs.system<Rel, const Velocity>() + .arg(1).object(flecs::Wildcard) + .arg(2).singleton() + .iter([&](flecs::iter it){ + count += it.count(); + }); + + ecs.entity().add<Rel, Tag>(); + ecs.set<Velocity>({}); + + s.run(); + + test_int(count, 1); +} + +void QueryBuilder_optional_tag_is_set() { + flecs::world ecs; + + struct TagA { }; + struct TagB { }; + + auto q = ecs.query_builder() + .term<TagA>() + .term<TagB>().oper(flecs::Optional) + .build(); + + auto e_1 = ecs.entity().add<TagA>().add<TagB>(); + auto e_2 = ecs.entity().add<TagA>(); + + int count = 0; + + q.iter([&](flecs::iter& it) { + test_int(it.count(), 1); + + count += it.count(); + + if (it.entity(0) == e_1) { + test_bool(it.is_set(1), true); + test_bool(it.is_set(2), true); + } else { + test_assert(it.entity(0) == e_2); + test_bool(it.is_set(1), true); + test_bool(it.is_set(2), false); + } + }); + + test_int(count, 2); +} + +void QueryBuilder_10_terms() { + flecs::world ecs; + + auto f = ecs.query_builder<>() + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .build(); + + test_int(f.term_count(), 10); + + auto e = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>(); + + int count = 0; + f.iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 10); + count ++; + }); + + test_int(count, 1); +} + +void QueryBuilder_20_terms() { + flecs::world ecs; + + auto f = ecs.query_builder<>() + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .term<TagK>() + .term<TagL>() + .term<TagM>() + .term<TagN>() + .term<TagO>() + .term<TagP>() + .term<TagQ>() + .term<TagR>() + .term<TagS>() + .term<TagT>() + .build(); + + test_int(f.term_count(), 20); + + auto e = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>() + .add<TagK>() + .add<TagL>() + .add<TagM>() + .add<TagN>() + .add<TagO>() + .add<TagP>() + .add<TagQ>() + .add<TagR>() + .add<TagS>() + .add<TagT>(); + + int count = 0; + f.iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 20); + count ++; + }); + + test_int(count, 1); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Refs.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Refs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8d770284068e8df9106a229e5bf4a497d5780f7a --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Refs.cpp @@ -0,0 +1,64 @@ +#include <cpp_api.h> + +void Refs_get_ref() { + flecs::world world; + + auto e = flecs::entity(world) + .set<Position>({10, 20}); + + auto ref = e.get_ref<Position>(); + test_assert(ref->x == 10); + test_assert(ref->y == 20); +} + +void Refs_ref_after_add() { + flecs::world world; + + auto e = flecs::entity(world) + .set<Position>({10, 20}); + + auto ref = e.get_ref<Position>(); + + e.add<Velocity>(); + test_assert(ref->x == 10); + test_assert(ref->y == 20); +} + +void Refs_ref_after_remove() { + flecs::world world; + + auto e = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 1}); + + auto ref = e.get_ref<Position>(); + + e.remove<Velocity>(); + test_assert(ref->x == 10); + test_assert(ref->y == 20); +} + +void Refs_ref_after_set() { + flecs::world world; + + auto e = flecs::entity(world) + .set<Position>({10, 20}); + + auto ref = e.get_ref<Position>(); + + e.set<Velocity>({1, 1}); + test_assert(ref->x == 10); + test_assert(ref->y == 20); +} + +void Refs_ref_before_set() { + flecs::world world; + + auto e = flecs::entity(world); + auto ref = e.get_ref<Position>(); + + e.set<Position>({10, 20}); + + test_assert(ref->x == 10); + test_assert(ref->y == 20); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Singleton.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Singleton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5a1ae4686bdf4740a2d97e81993ac6b64b2846be --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Singleton.cpp @@ -0,0 +1,166 @@ +#include <cpp_api.h> + +void Singleton_set_get_singleton() { + flecs::world world; + + world.set<Position>({10, 20}); + + const Position *p = world.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Singleton_get_mut_singleton() { + flecs::world world; + + Position *p_mut = world.get_mut<Position>(); + p_mut->x = 10; + p_mut->y = 20; + + const Position *p = world.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Singleton_emplace_singleton() { + flecs::world world; + + world.emplace<Position>(10.0f, 20.0f); + + const Position *p = world.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Singleton_modified_singleton() { + flecs::world world; + + int invoked = 0; + + world.system<Position>() + .kind(flecs::OnSet) + .iter([&](flecs::iter it, Position *p) { + invoked ++; + }); + + auto e = world.entity(); + Position *p = e.get_mut<Position>(); + test_assert(p != NULL); + test_int(invoked, 0); + + e.modified<Position>(); + test_int(invoked, 1); +} + +void Singleton_patch_singleton() { + flecs::world world; + + world.patch<Position>([](Position& p) { + p.x = 10; + p.y = 20; + }); + + const Position *p = world.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Singleton_add_singleton() { + flecs::world world; + + int invoked = 0; + + world.system<Position>() + .kind(flecs::OnAdd) + .iter([&](flecs::iter it, Position *p) { + invoked ++; + }); + + world.add<Position>(); + + test_int(invoked, 1); +} + + +void Singleton_remove_singleton() { + flecs::world world; + + int invoked = 0; + + world.system<Position>() + .kind(flecs::OnRemove) + .iter([&](flecs::iter it, Position *p) { + invoked ++; + }); + + Position *p_mut = world.get_mut<Position>(); + test_assert(p_mut != NULL); + + world.remove<Position>(); + + test_int(invoked, 1); +} + +void Singleton_has_singleton() { + flecs::world world; + + test_assert(!world.has<Position>()); + + world.set<Position>({10, 20}); + + test_assert(world.has<Position>()); +} + +void Singleton_singleton_system() { + flecs::world world; + + world.set<Position>({10, 20}); + + world.system<>(nullptr, "[inout] $Position") + .iter([](flecs::iter it) { + auto p = it.term<Position>(1); + test_int(p->x, 10); + test_int(p->y, 20); + + p->x ++; + p->y ++; + }); + + world.progress(); + + const Position *p = world.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 11); + test_int(p->y, 21); +} + +void Singleton_get_singleton() { + flecs::world world; + + world.set<Position>({10, 20}); + + auto s = world.singleton<Position>(); + test_assert(s.has<Position>()); + test_assert(s.id() == flecs::type_id<Position>()); + + const Position* p = s.get<Position>(); + test_int(p->x, 10); + test_int(p->y, 20); +} + +void Singleton_type_id_from_world() { + flecs::world world; + + world.set<Position>({10, 20}); + + flecs::entity_t id = world.type_id<Position>(); + test_assert(id == flecs::type_id<Position>()); + + auto s = world.singleton<Position>(); + test_assert(s.id() == flecs::type_id<Position>()); + test_assert(s.id() == flecs::type_id<Position>()); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Snapshot.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Snapshot.cpp new file mode 100644 index 0000000000000000000000000000000000000000..82865bca57b0685d633144f33ee7311566d4564a --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Snapshot.cpp @@ -0,0 +1,79 @@ +#include <cpp_api.h> + +void Snapshot_simple_snapshot() { + flecs::world world; + + auto e = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 1}); + + flecs::snapshot s(world); + s.take(); + + e.set<Position>({30, 40}); + e.set<Velocity>({2, 2}); + + s.restore(); + + const Position *p = e.get<Position>(); + const Velocity *v = e.get<Velocity>(); + + test_assert(p != NULL); + test_assert(v != NULL); + + test_int(p->x, 10); + test_int(p->y, 20); + + test_int(v->x, 1); + test_int(v->y, 1); +} + +void Snapshot_snapshot_iter() { + flecs::world world; + + auto e1 = flecs::entity(world) + .set<Position>({10, 20}) + .set<Velocity>({1, 1}); + + auto e2 = flecs::entity(world) + .set<Position>({30, 40}) + .set<Velocity>({2, 2}); + + flecs::snapshot s(world); + s.take(); + + bool e1_found = false; + bool e2_found = false; + + for (auto it : s) { + for (auto i : it) { + auto e = it.entity(i); + if (e == e1) { + e1_found = true; + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 1); + test_int(v->y, 1); + } else if (e == e2) { + e2_found = true; + const Position *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 30); + test_int(p->y, 40); + + const Velocity *v = e.get<Velocity>(); + test_assert(v != NULL); + test_int(v->x, 2); + test_int(v->y, 2); + } + } + } + + test_bool(e1_found, true); + test_bool(e2_found, true); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Switch.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Switch.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64b9bafc6d64a12039da93badf04c235b623483d --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Switch.cpp @@ -0,0 +1,214 @@ +#include <cpp_api.h> + +void Switch_add_case() { + flecs::world world; + + auto Standing = flecs::entity(world, "Standing"); + auto Walking = flecs::entity(world, "Walking"); + auto Movement = flecs::type(world, "Movement", + "Standing, Walking"); + + auto e = flecs::entity(world) + .add_switch(Movement) + .add_case(Standing); + + test_assert(e.has_switch(Movement)); + test_assert(e.has_case(Standing)); + + e.add_case(Walking); + + test_assert(e.has_case(Walking)); + test_assert(!e.has_case(Standing)); +} + +void Switch_get_case() { + flecs::world world; + + auto Standing = flecs::entity(world, "Standing"); + flecs::entity(world, "Walking"); + auto Movement = flecs::type(world, "Movement", + "Standing, Walking"); + + auto e = flecs::entity(world) + .add_switch(Movement) + .add_case(Standing); + + test_assert(e.has_switch(Movement)); + test_assert(e.has_case(Standing)); + + test_assert(e.get_case(Movement) == Standing); +} + +void Switch_system_w_case() { + flecs::world world; + + auto Standing = flecs::entity(world, "Standing"); + auto Walking = flecs::entity(world, "Walking"); + auto Movement = flecs::type(world, "Movement", + "Standing, Walking"); + + flecs::entity(world) + .add_switch(Movement) + .add_case(Walking); + + flecs::entity(world) + .add_switch(Movement) + .add_case(Walking); + + flecs::entity(world) + .add_switch(Movement) + .add_case(Standing); + + int count = 0, invoke_count = 0; + world.system<>(nullptr, "CASE | Walking") + .iter([&](flecs::iter it) { + auto movement = it.term<flecs::entity_t>(1); + + invoke_count ++; + for (auto i : it) { + test_assert(movement[i] == Walking.id()); + count ++; + } + }); + + world.progress(); + + test_int(invoke_count, 2); + test_int(count, 2); +} + +void Switch_system_w_switch() { + flecs::world world; + + auto Standing = flecs::entity(world, "Standing"); + auto Walking = flecs::entity(world, "Walking"); + auto Movement = flecs::type(world, "Movement", + "Standing, Walking"); + + auto e1 = flecs::entity(world) + .add_switch(Movement) + .add_case(Walking); + + auto e2 = flecs::entity(world) + .add_switch(Movement) + .add_case(Walking); + + auto e3 = flecs::entity(world) + .add_switch(Movement) + .add_case(Standing); + + int count = 0, invoke_count = 0; + world.system<>(nullptr, "SWITCH | Movement") + .iter([&](flecs::iter it) { + flecs::column<flecs::entity_t> movement(it, 1); + + invoke_count ++; + for (auto i : it) { + if (it.entity(i) == e1 || it.entity(i) == e2) { + test_int(movement[i], Walking.id()); + } else if (it.entity(i) == e3) { + test_int(movement[i], Standing.id()); + } + count ++; + } + }); + + world.progress(); + + test_int(invoke_count, 1); + test_int(count, 3); +} + +struct Movement { + struct Standing { }; + struct Walking { }; +}; + +void Switch_add_case_w_type() { + flecs::world world; + + auto Movement = world.type() + .add<Movement::Standing>() + .add<Movement::Walking>(); + + auto e = flecs::entity(world) + .add_switch(Movement) + .add_case<Movement::Standing>(); + + test_assert(e.has_switch(Movement)); + test_assert(e.has_case<Movement::Standing>()); + + e.add_case<Movement::Walking>(); + + test_assert(e.has_case<Movement::Walking>()); + test_assert(!e.has_case<Movement::Standing>()); +} + +void Switch_add_switch_w_type() { + flecs::world world; + + world.type() + .add<Movement::Standing>() + .add<Movement::Walking>() + .component<Movement>(); + + auto e = world.entity() + .add_switch<Movement>() + .add_case<Movement::Standing>(); + + test_assert(e.has_switch<Movement>()); + test_assert(e.has_case<Movement::Standing>()); + + e.add_case<Movement::Walking>(); + + test_assert(e.has_case<Movement::Walking>()); + test_assert(!e.has_case<Movement::Standing>()); +} + +void Switch_add_switch_w_type_component_first() { + flecs::world world; + + world.type().component<Movement>() + .add<Movement::Standing>() + .add<Movement::Walking>(); + + auto e = world.entity() + .add_switch<Movement>() + .add_case<Movement::Standing>(); + + test_assert(e.has_switch<Movement>()); + test_assert(e.has_case<Movement::Standing>()); + + e.add_case<Movement::Walking>(); + + test_assert(e.has_case<Movement::Walking>()); + test_assert(!e.has_case<Movement::Standing>()); +} + +void Switch_add_remove_switch_w_type() { + flecs::world world; + + world.type().component<Movement>() + .add<Movement::Standing>() + .add<Movement::Walking>(); + + auto e = world.entity() + .add_switch<Movement>() + .add_case<Movement::Standing>(); + + test_assert(e.has_switch<Movement>()); + test_assert(e.has_case<Movement::Standing>()); + + e.add_case<Movement::Walking>(); + + test_assert(e.has_case<Movement::Walking>()); + test_assert(!e.has_case<Movement::Standing>()); + + auto c = e.get_case<Movement>(); + test_assert(c != 0); + test_assert(c == world.id<Movement::Walking>()); + + e.remove_switch<Movement>(); + test_assert(!e.has_switch<Movement>()); + test_assert(!e.has_case<Movement::Walking>()); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/System.cpp b/fggl/ecs2/flecs/test/cpp_api/src/System.cpp new file mode 100644 index 0000000000000000000000000000000000000000..59a4e1b0cdad5d95be4a0514d864b5bb07c22349 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/System.cpp @@ -0,0 +1,1259 @@ +#include <cpp_api.h> + +void System_action() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + world.system<Position, Velocity>() + .iter([](flecs::iter&it, Position *p, Velocity *v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + world.progress(); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + const Velocity *v = entity.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void System_action_const() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + world.system<Position, const Velocity>() + .iter([](flecs::iter&it, Position *p, const Velocity* v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + world.progress(); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + const Velocity *v = entity.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void System_action_shared() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto base = world.entity() + .set<Velocity>({1, 2}); + + auto e1 = world.entity() + .set<Position>({10, 20}) + .add(flecs::IsA, base); + + auto e2 = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({3, 4}); + + world.system<Position>().signature("ANY:Velocity") + .iter([](flecs::iter&it, Position *p) { + auto v = it.term<const Velocity>(2); + + if (v.is_shared()) { + for (auto i : it) { + p[i].x += v->x; + p[i].y += v->y; + } + } else { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + } + }); + + world.progress(); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 13); + test_int(p->y, 24); +} + +void System_action_optional() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + flecs::component<Mass>(world, "Mass"); + + auto e1 = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .set<Mass>({1}); + + auto e2 = world.entity() + .set<Position>({30, 40}) + .set<Velocity>({3, 4}) + .set<Mass>({1}); + + auto e3 = world.entity() + .set<Position>({50, 60}); + + auto e4 = world.entity() + .set<Position>({70, 80}); + + world.system<Position, Velocity*, Mass*>() + .iter([](flecs::iter& it, Position *p, Velocity *v, Mass *m) { + if (it.is_set(2) && it.is_set(3)) { + for (auto i : it) { + p[i].x += v[i].x * m[i].value; + p[i].y += v[i].y * m[i].value; + } + } else { + for (auto i : it) { + p[i].x ++; + p[i].y ++; + } + } + }); + + world.progress(); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 33); + test_int(p->y, 44); + + p = e3.get<Position>(); + test_int(p->x, 51); + test_int(p->y, 61); + + p = e4.get<Position>(); + test_int(p->x, 71); + test_int(p->y, 81); +} + +void System_each() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + world.system<Position, Velocity>() + .each([](flecs::entity e, Position& p, Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + world.progress(); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void System_each_const() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + world.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + world.progress(); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void System_each_shared() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto base = world.entity() + .set<Velocity>({1, 2}); + + auto e1 = world.entity() + .set<Position>({10, 20}) + .add(flecs::IsA, base); + + auto e2 = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({3, 4}); + + world.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + world.progress(); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 13); + test_int(p->y, 24); +} + +void System_each_optional() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + flecs::component<Mass>(world, "Mass"); + + auto e1 = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .set<Mass>({1}); + + auto e2 = world.entity() + .set<Position>({30, 40}) + .set<Velocity>({3, 4}) + .set<Mass>({1}); + + auto e3 = world.entity() + .set<Position>({50, 60}); + + auto e4 = world.entity() + .set<Position>({70, 80}); + + world.system<Position, Velocity*, Mass*>() + .each([](flecs::entity e, Position& p, Velocity* v, Mass *m) { + if (v && m) { + p.x += v->x * m->value; + p.y += v->y * m->value; + } else { + p.x ++; + p.y ++; + } + }); + + world.progress(); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 33); + test_int(p->y, 44); + + p = e3.get<Position>(); + test_int(p->x, 51); + test_int(p->y, 61); + + p = e4.get<Position>(); + test_int(p->x, 71); + test_int(p->y, 81); +} + + +void System_signature() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + world.system<>().signature("Position, Velocity") + .iter([](flecs::iter&it) { + flecs::column<Position> p(it, 1); + flecs::column<Velocity> v(it, 2); + + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + world.progress(); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + const Velocity *v = entity.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void System_signature_const() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto entity = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + world.system<>().signature("Position, [in] Velocity") + .iter([](flecs::iter&it) { + flecs::column<Position> p(it, 1); + flecs::column<const Velocity> v(it, 2); + + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + world.progress(); + + const Position *p = entity.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + const Velocity *v = entity.get<Velocity>(); + test_int(v->x, 1); + test_int(v->y, 2); +} + +void System_signature_shared() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + + auto base = world.entity() + .set<Velocity>({1, 2}); + + auto e1 = world.entity() + .set<Position>({10, 20}) + .add(flecs::IsA, base); + + auto e2 = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({3, 4}); + + world.system<>().signature("Position, [in] ANY:Velocity") + .iter([](flecs::iter&it) { + flecs::column<Position> p(it, 1); + flecs::column<const Velocity> v(it, 2); + + if (v.is_shared()) { + for (auto i : it) { + p[i].x += v->x; + p[i].y += v->y; + } + } else { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + } + }); + + world.progress(); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 13); + test_int(p->y, 24); +} + +void System_signature_optional() { + flecs::world world; + + world.component<Position>("Position"); + world.component<Velocity>("Velocity"); + flecs::component<Mass>(world, "Mass"); + + auto e1 = world.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}) + .set<Mass>({1}); + + auto e2 = world.entity() + .set<Position>({30, 40}) + .set<Velocity>({3, 4}) + .set<Mass>({1}); + + auto e3 = world.entity() + .set<Position>({50, 60}); + + auto e4 = world.entity() + .set<Position>({70, 80}); + + world.system<>().signature("Position, ?Velocity, ?Mass") + .iter([](flecs::iter& it) { + flecs::column<Position> p(it, 1); + flecs::column<Velocity> v(it, 2); + flecs::column<Mass> m(it, 3); + + if (v.is_set() && m.is_set()) { + for (auto i : it) { + p[i].x += v[i].x * m[i].value; + p[i].y += v[i].y * m[i].value; + } + } else { + for (auto i : it) { + p[i].x ++; + p[i].y ++; + } + } + }); + + world.progress(); + + const Position *p = e1.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); + + p = e2.get<Position>(); + test_int(p->x, 33); + test_int(p->y, 44); + + p = e3.get<Position>(); + test_int(p->x, 51); + test_int(p->y, 61); + + p = e4.get<Position>(); + test_int(p->x, 71); + test_int(p->y, 81); +} + + +void System_copy_name_on_create() { + flecs::world world; + + char name[6]; + strcpy(name, "Hello"); + + auto system_1 = world.system<Position>(name) + .iter([](flecs::iter&it, Position *p) {}); + + strcpy(name, "World"); + auto system_2 = world.system<Position>(name) + .iter([](flecs::iter&it, Position *p) {}); + + test_assert(system_1.id() != system_2.id()); +} + +void System_nested_system() { + flecs::world world; + + auto system_1 = world.system<Position>("foo::bar") + .iter([](flecs::iter&it, Position *p) {}); + + test_str(system_1.name().c_str(), "bar"); + + auto e = world.lookup("foo"); + test_assert(e.id() != 0); + test_str(e.name().c_str(), "foo"); + + auto se = e.lookup("bar"); + test_assert(se.id() != 0); + test_str(se.name().c_str(), "bar"); +} + +void System_empty_signature() { + flecs::world world; + + int count = 0; + + world.system<>() + .iter([&](flecs::iter it) { + count ++; + }); + + world.progress(); + + test_int(count, 1); +} + +struct MyTag { }; + +void System_action_tag() { + flecs::world world; + + int invoked = 0; + + world.system<MyTag>() + .iter([&](flecs::iter it, MyTag*) { + invoked ++; + }); + + world.entity() + .add<MyTag>(); + + world.progress(); + + test_int(invoked, 1); +} + +void System_iter_tag() { + flecs::world world; + + int invoked = 0; + + world.system<MyTag>() + .iter([&](flecs::iter it, MyTag*) { + invoked ++; + }); + + world.entity() + .add<MyTag>(); + + world.progress(); + + test_int(invoked, 1); +} + +void System_each_tag() { + flecs::world world; + + int invoked = 0; + + world.system<MyTag>() + .each([&](flecs::entity e, MyTag&) { + invoked ++; + }); + + world.entity() + .add<MyTag>(); + + world.progress(); + + test_int(invoked, 1); +} + +void System_system_from_id() { + flecs::world world; + + uint32_t invoked = 0; + flecs::entity sys = world.system<>() + .kind(0) + .iter([&](flecs::iter& it) { + invoked ++; + }); + + auto sys_from_id = world.system(sys); + + sys_from_id.run(); + test_int(invoked, 1); +} + +void System_set_interval() { + flecs::world world; + + auto sys = world.system<>() + .kind(0) + .interval(1.0f) + .iter([&](flecs::iter& it) { }); + + float i = sys.interval(); + test_int(i, 1.0f); + + sys.interval(2.0f); + + i = sys.interval(); + test_int(i, 2.0f); +} + +void System_order_by_type() { + flecs::world world; + + world.entity().set<Position>({3, 0}); + world.entity().set<Position>({1, 0}); + world.entity().set<Position>({5, 0}); + world.entity().set<Position>({2, 0}); + world.entity().set<Position>({4, 0}); + + float last_val = 0; + int32_t count = 0; + + auto sys = world.system<const Position>() + .order_by<Position>( + [](flecs::entity_t e1, const Position *p1, + flecs::entity_t e2, const Position *p2) { + return (p1->x > p2->x) - (p1->x < p2->x); + }) + .each([&](flecs::entity e, const Position& p) { + test_assert(p.x > last_val); + last_val = p.x; + count ++; + }); + + sys.run(); + + test_int(count, 5); +} + +void System_order_by_id() { + flecs::world world; + + auto pos = world.component<Position>(); + + world.entity().set<Position>({3, 0}); + world.entity().set<Position>({1, 0}); + world.entity().set<Position>({5, 0}); + world.entity().set<Position>({2, 0}); + world.entity().set<Position>({4, 0}); + + float last_val = 0; + int32_t count = 0; + + auto sys = world.system<const Position>() + .order_by(pos, [](flecs::entity_t e1, const void *p1, + flecs::entity_t e2, const void *p2) + { + return (static_cast<const Position*>(p1)->x > + static_cast<const Position*>(p2)->x) - + (static_cast<const Position*>(p1)->x < + static_cast<const Position*>(p2)->x); + }) + .each([&](flecs::entity e, const Position& p) { + test_assert(p.x > last_val); + last_val = p.x; + count ++; + }); + + sys.run(); + + test_int(count, 5); +} + +void System_order_by_type_after_create() { + flecs::world world; + + world.entity().set<Position>({3, 0}); + world.entity().set<Position>({1, 0}); + world.entity().set<Position>({5, 0}); + world.entity().set<Position>({2, 0}); + world.entity().set<Position>({4, 0}); + + float last_val = 0; + int32_t count = 0; + + auto sys = world.system<const Position>() + .each([&](flecs::entity e, const Position& p) { + test_assert(p.x > last_val); + last_val = p.x; + count ++; + }); + + sys.order_by<Position>( + [](flecs::entity_t e1, const Position *p1, + flecs::entity_t e2, const Position *p2) { + return (p1->x > p2->x) - (p1->x < p2->x); + }); + + sys.run(); + + test_int(count, 5); +} + +void System_order_by_id_after_create() { + flecs::world world; + + auto pos = world.component<Position>(); + + world.entity().set<Position>({3, 0}); + world.entity().set<Position>({1, 0}); + world.entity().set<Position>({5, 0}); + world.entity().set<Position>({2, 0}); + world.entity().set<Position>({4, 0}); + + float last_val = 0; + int32_t count = 0; + + auto sys = world.system<const Position>() + .each([&](flecs::entity e, const Position& p) { + test_assert(p.x > last_val); + last_val = p.x; + count ++; + }); + + sys.order_by(pos, [](flecs::entity_t e1, const void *p1, flecs::entity_t e2, const void *p2) { + return (static_cast<const Position*>(p1)->x > static_cast<const Position*>(p2)->x) - + (static_cast<const Position*>(p1)->x < static_cast<const Position*>(p2)->x); + }); + + sys.run(); + + test_int(count, 5); +} + +void System_get_query() { + flecs::world world; + + world.entity().set<Position>({0, 0}); + world.entity().set<Position>({1, 0}); + world.entity().set<Position>({2, 0}); + + int32_t count = 0; + + auto sys = world.system<const Position>() + .each([&](flecs::entity e, const Position& p) { + // Not used + }); + + auto q = sys.query(); + + q.iter([&](flecs::iter &it) { + auto pos = it.term<const Position>(1); + for (auto i : it) { + test_int(i, pos[i].x); + count ++; + } + }); + + test_int(count, 3); +} + +void System_add_from_each() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto e1 = world.entity().set<Position>({0, 0}); + auto e2 = world.entity().set<Position>({1, 0}); + auto e3 = world.entity().set<Position>({2, 0}); + + world.system<const Position>() + .each([](flecs::entity e, const Position& p) { + e.add<Velocity>(); + // Add is deferred + test_assert(!e.has<Velocity>()); + }); + + world.progress(); + + test_assert(e1.has<Velocity>()); + test_assert(e2.has<Velocity>()); + test_assert(e3.has<Velocity>()); +} + +void System_delete_from_each() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto e1 = world.entity().set<Position>({0, 0}); + auto e2 = world.entity().set<Position>({1, 0}); + auto e3 = world.entity().set<Position>({2, 0}); + + world.system<const Position>() + .each([](flecs::entity e, const Position& p) { + e.destruct(); + // Delete is deferred + test_assert(e.is_alive()); + }); + + world.progress(); + + test_assert(!e1.is_alive()); + test_assert(!e2.is_alive()); + test_assert(!e3.is_alive()); +} + +struct Entity { + flecs::entity e; +}; + +void System_add_from_each_world_handle() { + flecs::world world; + + auto e1 = world.entity().set<Entity>({world.entity()}); + auto e2 = world.entity().set<Entity>({world.entity()}); + auto e3 = world.entity().set<Entity>({world.entity()}); + + world.system<const Entity>() + .each([](flecs::entity e, const Entity& c) { + c.e.mut(e).add<Position>(); + }); + + world.progress(); + + test_assert(e1.get<Entity>()->e.has<Position>()); + test_assert(e2.get<Entity>()->e.has<Position>()); + test_assert(e3.get<Entity>()->e.has<Position>()); +} + +void System_new_from_each() { + flecs::world world; + + auto e1 = world.entity().set<Position>({0, 0}); + auto e2 = world.entity().set<Position>({0, 0}); + auto e3 = world.entity().set<Position>({0, 0}); + + world.system<const Position>() + .each([](flecs::entity e, const Position& p) { + e.set<Entity>({ + e.world().entity().add<Velocity>() + }); + }); + + world.progress(); + + test_assert(e1.has<Entity>()); + test_assert(e2.has<Entity>()); + test_assert(e3.has<Entity>()); + + test_assert(e1.get<Entity>()->e.has<Velocity>()); + test_assert(e2.get<Entity>()->e.has<Velocity>()); + test_assert(e3.get<Entity>()->e.has<Velocity>()); +} + +void System_add_from_iter() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto e1 = world.entity().set<Position>({0, 0}); + auto e2 = world.entity().set<Position>({1, 0}); + auto e3 = world.entity().set<Position>({2, 0}); + + world.system<const Position>() + .iter([](flecs::iter& it, const Position* p) { + for (auto i : it) { + it.entity(i).add<Velocity>(); + test_assert(!it.entity(i).has<Velocity>()); + } + }); + + world.progress(); + + test_assert(e1.has<Velocity>()); + test_assert(e2.has<Velocity>()); + test_assert(e3.has<Velocity>()); +} + +void System_delete_from_iter() { + flecs::world world; + + world.component<Position>(); + world.component<Velocity>(); + + auto e1 = world.entity().set<Position>({0, 0}); + auto e2 = world.entity().set<Position>({1, 0}); + auto e3 = world.entity().set<Position>({2, 0}); + + world.system<const Position>() + .iter([](const flecs::iter& it, const Position* p) { + for (auto i : it) { + it.entity(i).destruct(); + // Delete is deferred + test_assert(it.entity(i).is_alive()); + } + }); + + world.progress(); + + test_assert(!e1.is_alive()); + test_assert(!e2.is_alive()); + test_assert(!e3.is_alive()); +} + +void System_add_from_iter_world_handle() { + flecs::world world; + + auto e1 = world.entity().set<Entity>({world.entity()}); + auto e2 = world.entity().set<Entity>({world.entity()}); + auto e3 = world.entity().set<Entity>({world.entity()}); + + world.system<const Entity>() + .iter([](const flecs::iter& it, const Entity* c) { + for (auto i : it) { + c[i].e.mut(it).add<Position>(); + } + }); + + world.progress(); + + test_assert(e1.get<Entity>()->e.has<Position>()); + test_assert(e2.get<Entity>()->e.has<Position>()); + test_assert(e3.get<Entity>()->e.has<Position>()); +} + +void System_new_from_iter() { + flecs::world world; + + auto e1 = world.entity().set<Position>({0, 0}); + auto e2 = world.entity().set<Position>({0, 0}); + auto e3 = world.entity().set<Position>({0, 0}); + + world.system<const Position>() + .iter([](const flecs::iter& it, const Position* p) { + for (auto i : it) { + it.entity(i).set<Entity>({ + it.world().entity().add<Velocity>() + }); + } + }); + + world.progress(); + + test_assert(e1.has<Entity>()); + test_assert(e2.has<Entity>()); + test_assert(e3.has<Entity>()); + + test_assert(e1.get<Entity>()->e.has<Velocity>()); + test_assert(e2.get<Entity>()->e.has<Velocity>()); + test_assert(e3.get<Entity>()->e.has<Velocity>()); +} + +void System_each_w_mut_children_it() { + flecs::world world; + + auto parent = world.entity().set<Position>({0, 0}); + auto e1 = world.entity().set<Position>({0, 0}).child_of(parent); + auto e2 = world.entity().set<Position>({0, 0}).child_of(parent); + auto e3 = world.entity().set<Position>({0, 0}).child_of(parent); + + world.system<const Position>() + .iter([](const flecs::iter& it, const Position* p) { + for (auto i : it) { + for (flecs::iter child_it : it.entity(i).children()) { + for (auto c : child_it) { + child_it.entity(c).add<Velocity>(); + } + } + } + }); + + world.progress(); + + test_assert(e1.has<Velocity>()); + test_assert(e2.has<Velocity>()); + test_assert(e3.has<Velocity>()); +} + +void System_readonly_children_iter() { + flecs::world world; + + auto parent = world.entity(); + world.entity().set<Entity>({ parent }); + world.entity().set<Position>({1, 0}).child_of(parent); + world.entity().set<Position>({1, 0}).child_of(parent); + world.entity().set<Position>({1, 0}).child_of(parent); + + world.system<const Entity>() + .iter([](const flecs::iter& it, const Entity* c) { + for (auto i : it) { + for (flecs::iter child_it : c[i].e.children()) { + for (auto c : child_it) { + // Dummy code to ensure we can access the entity + const Position *p = child_it.entity(c).get<Position>(); + test_int(p->x, 1); + test_int(p->y, 0); + } + } + } + }); + + world.progress(); +} + +void System_rate_filter() { + flecs::world world; + + int32_t + root_count = 0, root_mult = 1, + l1_a_count = 0, l1_a_mult = 1, + l1_b_count = 0, l1_b_mult = 2, + l1_c_count = 0, l1_c_mult = 3, + l2_a_count = 0, l2_a_mult = 2, + l2_b_count = 0, l2_b_mult = 4, + frame_count = 0; + + auto root = world.system<>("root") + .iter([&](flecs::iter& it) { + root_count ++; + }); + + auto l1_a = world.system<>("l1_a") + .rate(root, 1) + .iter([&](flecs::iter& it) { + l1_a_count ++; + }); + + auto l1_b = world.system<>("l1_b") + .rate(root, 2) + .iter([&](flecs::iter& it) { + l1_b_count ++; + }); + + world.system<>("l1_c") + .rate(root, 3) + .iter([&](flecs::iter& it) { + l1_c_count ++; + }); + + world.system<>("l2_a") + .rate(l1_a, 2) + .iter([&](flecs::iter& it) { + l2_a_count ++; + }); + + world.system<>("l2_b") + .rate(l1_b, 2) + .iter([&](flecs::iter& it) { + l2_b_count ++; + }); + + for (int i = 0; i < 30; i ++) { + world.progress(); frame_count ++; + test_int(root_count, frame_count / root_mult); + test_int(l1_a_count, frame_count / l1_a_mult); + test_int(l1_b_count, frame_count / l1_b_mult); + test_int(l1_c_count, frame_count / l1_c_mult); + test_int(l2_a_count, frame_count / l2_a_mult); + test_int(l2_b_count, frame_count / l2_b_mult); + } +} + +void System_update_rate_filter() { + flecs::world world; + + int32_t + root_count = 0, root_mult = 1, + l1_count = 0, l1_mult = 2, + l2_count = 0, l2_mult = 6, + frame_count = 0; + + auto root = world.system<>("root") + .iter([&](flecs::iter& it) { + root_count ++; + }); + + auto l1 = world.system<>("l1") + .rate(root, 2) + .iter([&](flecs::iter& it) { + l1_count ++; + }); + + world.system<>("l2") + .rate(l1, 3) + .iter([&](flecs::iter& it) { + l2_count ++; + }); + + for (int i = 0; i < 12; i ++) { + world.progress(); frame_count ++; + test_int(root_count, frame_count / root_mult); + test_int(l1_count, frame_count / l1_mult); + test_int(l2_count, frame_count / l2_mult); + } + + l1.rate(4); // Run twice as slow + l1_mult *= 2; + l2_mult *= 2; + + frame_count = 0; + l1_count = 0; + l2_count = 0; + root_count = 0; + + for (int i = 0; i < 32; i ++) { + world.progress(); frame_count ++; + test_int(root_count, frame_count / root_mult); + test_int(l1_count, frame_count / l1_mult); + test_int(l2_count, frame_count / l2_mult); + } +} + +void System_default_ctor() { + flecs::world world; + + flecs::system<Position> sys_var; + + int count = 0; + auto sys = world.system<Position>() + .each([&](flecs::entity e, Position& p) { + test_int(p.x, 10); + test_int(p.y, 20); + count ++; + }); + + world.entity().set<Position>({10, 20}); + + sys_var = sys; + + sys_var.run(); + + test_int(count, 1); +} + +void System_test_auto_defer_each() { + flecs::world world; + + struct Value { int value; }; + + auto e1 = world.entity().add<Tag>().set<Value>({10}); + auto e2 = world.entity().add<Tag>().set<Value>({20}); + auto e3 = world.entity().add<Tag>().set<Value>({30}); + + auto s = world.system<Value>() + .term<Tag>() + .each([](flecs::entity e, Value& v) { + v.value ++; + e.remove<Tag>(); + }); + + s.run(); + + test_assert(!e1.has<Tag>()); + test_assert(!e2.has<Tag>()); + test_assert(!e3.has<Tag>()); + + test_assert(e1.has<Value>()); + test_assert(e2.has<Value>()); + test_assert(e3.has<Value>()); + + test_int(e1.get<Value>()->value, 11); + test_int(e2.get<Value>()->value, 21); + test_int(e3.get<Value>()->value, 31); +} + +void System_test_auto_defer_iter() { + flecs::world world; + + struct Value { int value; }; + + auto e1 = world.entity().add<Tag>().set<Value>({10}); + auto e2 = world.entity().add<Tag>().set<Value>({20}); + auto e3 = world.entity().add<Tag>().set<Value>({30}); + + auto s = world.system<Value>() + .term<Tag>() + .iter([](flecs::iter& it, Value *v) { + for (auto i : it) { + v[i].value ++; + it.entity(i).remove<Tag>(); + } + }); + + s.run(); + + test_assert(!e1.has<Tag>()); + test_assert(!e2.has<Tag>()); + test_assert(!e3.has<Tag>()); + + test_assert(e1.has<Value>()); + test_assert(e2.has<Value>()); + test_assert(e3.has<Value>()); + + test_int(e1.get<Value>()->value, 11); + test_int(e2.get<Value>()->value, 21); + test_int(e3.get<Value>()->value, 31); +} + +void System_custom_pipeline() { + flecs::world world; + + auto PreFrame = world.entity(); + auto OnFrame = world.entity(); + auto PostFrame = world.entity(); + + flecs::pipeline pip = world.pipeline("FooPipeline") + .add(PreFrame) + .add(OnFrame) + .add(PostFrame); + + int count = 0; + + world.system<>() + .kind(PostFrame) + .iter([&](flecs::iter it) { + test_int(count, 2); + count ++; + }); + + world.system<>() + .kind(OnFrame) + .iter([&](flecs::iter it) { + test_int(count, 1); + count ++; + }); + + world.system<>() + .kind(PreFrame) + .iter([&](flecs::iter it) { + test_int(count, 0); + count ++; + }); + + test_int(count, 0); + + world.set_pipeline(pip); + world.progress(); + + test_int(count, 3); +} + +void System_system_w_self() { + flecs::world world; + + auto self = world.entity(); + + bool invoked = false; + auto sys = world.system<Position>() + .self(self) + .iter([&](flecs::iter& it) { + test_assert(it.self() == self); + invoked = true; + }); + + world.entity().set<Position>({10, 20}); + + sys.run(); + + test_bool(invoked, true); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/SystemBuilder.cpp b/fggl/ecs2/flecs/test/cpp_api/src/SystemBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab515d7eab0542e718c123554c927626fd246602 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/SystemBuilder.cpp @@ -0,0 +1,471 @@ +#include <cpp_api.h> + +void SystemBuilder_builder_assign_same_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + + flecs::system<Position, Velocity> s = + ecs.system<Position, Velocity>() + .each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_builder_assign_from_empty() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + + flecs::system<> s = ecs.system<>() + .term<Position>() + .term<Velocity>() + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_builder_build_to_auto() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + + auto s = ecs.system<Position, Velocity>() + .each([&](flecs::entity e, Position& p, Velocity& v) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_builder_build_n_statements() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Position>(); + + int32_t count = 0; + + auto qb = ecs.system<>(); + qb.term<Position>(); + qb.term<Velocity>(); + auto s = qb.each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + s.run(); + + test_int(count, 1); +} + +void SystemBuilder_1_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<Position>() + .each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_1_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<>() + .term<Position>() + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_2_types() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<>() + .term<Position>() + .term<Velocity>() + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_1_type_w_1_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<Position>() + .term<Velocity>() + .each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_2_types_w_1_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>().add<Velocity>().add<Mass>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<Position>() + .term<Velocity>() + .term<Mass>() + .each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_pair() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + auto Alice = ecs.entity(); + + auto e1 = ecs.entity().add(Likes, Bob); + ecs.entity().add(Likes, Alice); + + int32_t count = 0; + + auto s = ecs.system<>() + .term(Likes, Bob) + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_not() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<Position>() + .term<Velocity>().oper(flecs::Not) + .each([&](flecs::entity e, Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_add_or() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Velocity>(); + ecs.entity().add<Mass>(); + + int32_t count = 0; + + auto s = ecs.system<>() + .term<Position>().oper(flecs::Or) + .term<Velocity>().oper(flecs::Or) + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 0); + s.run(); + test_int(count, 2); +} + +void SystemBuilder_add_optional() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>().add<Mass>(); + + int32_t count = 0; + + auto s = ecs.system<>() + .term<Position>() + .term<Velocity>().oper(flecs::Optional) + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 0); + s.run(); + test_int(count, 2); +} + +void SystemBuilder_ptr_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + auto e2 = ecs.entity().add<Position>().add<Velocity>(); + ecs.entity().add<Velocity>().add<Mass>(); + + int32_t count = 0; + + auto s = ecs.system<Position, Velocity*>() + .each([&](flecs::entity e, Position& p, Velocity* v) { + count ++; + test_assert(e == e1 || e == e2); + }); + + test_int(count, 0); + s.run(); + test_int(count, 2); +} + +void SystemBuilder_const_type() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<const Position>() + .each([&](flecs::entity e, const Position& p) { + count ++; + test_assert(e == e1); + }); + + test_int(count, 0); + s.run(); + test_int(count, 1); +} + +void SystemBuilder_string_term() { + flecs::world ecs; + + auto e1 = ecs.entity().add<Position>(); + ecs.entity().add<Velocity>(); + + int32_t count = 0; + + auto s = ecs.system<>() + .term("Position") + .each([&](flecs::entity e) { + count ++; + test_assert(e == e1); + }); + + s.run(); + + test_int(count, 1); +} + +void SystemBuilder_singleton_term() { + flecs::world ecs; + + struct Entity { + flecs::entity_view value; + }; + + struct Singleton { + int32_t value; + }; + + ecs.set<Singleton>({10}); + + int32_t count = 0; + + auto s = ecs.system<Entity>() + .term<Singleton>().singleton() + .iter([&](flecs::iter& it, Entity *e) { + auto s = it.term<const Singleton>(2); + test_assert(!s.is_owned()); + test_int(s->value, 10); + + const Singleton& s_ref = *s; + test_int(s_ref.value, 10); + + for (auto i : it) { + test_assert(it.entity(i) == e[i].value); + count ++; + } + }); + + auto + e = ecs.entity(); e.set<Entity>({e}); + e = ecs.entity(); e.set<Entity>({e}); + e = ecs.entity(); e.set<Entity>({e}); + + s.run(); + + test_int(count, 3); +} + +void SystemBuilder_10_terms() { + flecs::world ecs; + + int count = 0; + + auto e = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>(); + + auto s = ecs.system<>() + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 10); + count ++; + }); + + s.run(); + + test_int(count, 1); +} + +void SystemBuilder_20_terms() { + flecs::world ecs; + + int count = 0; + + auto e = ecs.entity() + .add<TagA>() + .add<TagB>() + .add<TagC>() + .add<TagD>() + .add<TagE>() + .add<TagF>() + .add<TagG>() + .add<TagH>() + .add<TagI>() + .add<TagJ>() + .add<TagK>() + .add<TagL>() + .add<TagM>() + .add<TagN>() + .add<TagO>() + .add<TagP>() + .add<TagQ>() + .add<TagR>() + .add<TagS>() + .add<TagT>(); + + auto s = ecs.system<>() + .term<TagA>() + .term<TagB>() + .term<TagC>() + .term<TagD>() + .term<TagE>() + .term<TagF>() + .term<TagG>() + .term<TagH>() + .term<TagI>() + .term<TagJ>() + .term<TagK>() + .term<TagL>() + .term<TagM>() + .term<TagN>() + .term<TagO>() + .term<TagP>() + .term<TagQ>() + .term<TagR>() + .term<TagS>() + .term<TagT>() + .iter([&](flecs::iter& it) { + test_int(it.count(), 1); + test_assert(it.entity(0) == e); + test_int(it.term_count(), 20); + count ++; + }); + + s.run(); + + test_int(count, 1); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Trigger.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Trigger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7212afd2c1027eb94717cfa58df4e41720b1085b --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Trigger.cpp @@ -0,0 +1,111 @@ +#include <cpp_api.h> + +void Trigger_on_add() { + flecs::world world; + + int invoked = 0; + + world.system<Position>() + .kind(flecs::OnAdd) + .each([&](flecs::entity e, Position& p) { + invoked ++; + }); + + world.entity() + .add<Position>(); + + test_int(invoked, 1); +} + +void Trigger_on_remove() { + flecs::world world; + + int invoked = 0; + + world.system<Position>() + .kind(flecs::OnRemove) + .each([&](flecs::entity e, Position& p) { + invoked ++; + }); + + auto e = world.entity() + .add<Position>(); + + test_int(invoked, 0); + + e.remove<Position>(); + + test_int(invoked, 1); +} + +struct MyTag { }; + +void Trigger_on_add_tag_action() { + flecs::world world; + + int invoked = 0; + + world.system<MyTag>() + .kind(flecs::OnAdd) + .iter([&](flecs::iter it, MyTag*) { + invoked ++; + }); + + world.entity() + .add<MyTag>(); + + test_int(invoked, 1); +} + +void Trigger_on_add_tag_iter() { + flecs::world world; + + int invoked = 0; + + world.system<MyTag>() + .kind(flecs::OnAdd) + .iter([&](flecs::iter it, MyTag*) { + invoked ++; + }); + + world.entity() + .add<MyTag>(); + + test_int(invoked, 1); +} + +void Trigger_on_add_tag_each() { + flecs::world world; + + int invoked = 0; + + world.system<MyTag>() + .kind(flecs::OnAdd) + .each([&](flecs::entity e, MyTag&) { + invoked ++; + }); + + world.entity() + .add<MyTag>(); + + test_int(invoked, 1); +} + +void Trigger_trigger_w_self() { + flecs::world world; + + auto self = world.entity(); + + bool invoked = false; + world.system<Position>() + .kind(flecs::OnAdd) + .self(self) + .iter([&](flecs::iter& it) { + test_assert(it.self() == self); + invoked = true; + }); + + world.entity().set<Position>({10, 20}); + + test_bool(invoked, true); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/Type.cpp b/fggl/ecs2/flecs/test/cpp_api/src/Type.cpp new file mode 100644 index 0000000000000000000000000000000000000000..68ce3e56911dff2b827706536e6bf548e7b4aeba --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/Type.cpp @@ -0,0 +1,231 @@ +#include <cpp_api.h> + +void Type_add_2() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto type = flecs::type(world) + .add<Position>() + .add<Velocity>(); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); +} + +void Type_add_instanceof() { + flecs::world world; + + auto base = flecs::entity(world); + auto type = flecs::type(world) + .add(flecs::IsA, base); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has(flecs::IsA, base.id())); +} + +void Type_add_childof() { + flecs::world world; + + auto parent = flecs::entity(world); + auto type = flecs::type(world) + .child_of(parent); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has(flecs::ChildOf, parent.id())); +} + +void Type_1_component() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + + auto type = flecs::type(world).add<Position>(); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has<Position>()); +} + +void Type_2_component() { + flecs::world world; + + auto type = flecs::type(world) + .add<Position>() + .add<Velocity>(); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); +} + +void Type_1_component_signature() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + + auto type = flecs::type(world, nullptr, "Position"); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has<Position>()); +} + +void Type_2_component_signature() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + flecs::component<Velocity>(world, "Velocity"); + + auto type = flecs::type(world, nullptr, "Position, Velocity"); + + auto entity = flecs::entity(world); + test_assert(entity.id() != 0); + + entity.add(type); + test_assert(entity.has<Position>()); + test_assert(entity.has<Velocity>()); +} + +void Type_type_no_name() { + flecs::world world; + + flecs::component<Position>(world, "Position"); + + auto type = flecs::type(world, nullptr, "Position"); + auto id = type.id(); + auto e = world.entity(id); + test_assert(!e.has<flecs::Identifier>(flecs::Name)); +} + +void Type_null_args() { + flecs::world world; + + flecs::type t(); + + // Make sure code didn't crash + test_assert(true); +} + +void Type_has_type() { + flecs::world world; + + auto type = world.type() + .add<Position>() + .add<Velocity>(); + + test_assert(type.has<Position>()); + test_assert(type.has<Velocity>()); + test_assert(!type.has<Mass>()); +} + +void Type_has_entity() { + flecs::world world; + + auto e1 = world.entity(); + auto e2 = world.entity(); + auto e3 = world.entity(); + + auto type = world.type() + .add(e1) + .add(e2); + + test_assert(type.has(e1)); + test_assert(type.has(e2)); + test_assert(!type.has(e3)); +} + +void Type_has_pair_type() { + flecs::world world; + + struct Eats {}; + struct Apples {}; + struct Pears {}; + struct Bananas {}; + + auto type = world.type() + .add<Eats, Apples>() + .add<Eats, Pears>(); + + test_assert((type.has<Eats, Apples>())); + test_assert((type.has<Eats, Pears>())); + test_assert((!type.has<Eats, Bananas>())); +} + +void Type_has_pair_entity() { + flecs::world world; + + auto eats = world.entity(); + auto apples = world.entity(); + auto pears = world.entity(); + auto bananas = world.entity(); + + auto type = world.type() + .add(eats, apples) + .add(eats, pears); + + test_assert(type.has(eats, apples)); + test_assert(type.has(eats, pears)); + test_assert(!type.has(eats, bananas)); +} + +void Type_get() { + flecs::world world; + + auto apples = world.entity(); + auto pears = world.entity(); + auto bananas = world.entity(); + + auto type = world.type() + .add(apples) + .add(pears) + .add(bananas); + + test_assert(type.has(apples)); + test_assert(type.has(pears)); + test_assert(type.has(bananas)); + + test_assert(type.get(0) == apples); + test_assert(type.get(1) == pears); + test_assert(type.get(2) == bananas); +} + +void Type_get_out_of_range() { + install_test_abort(); + + flecs::world world; + + auto apples = world.entity(); + auto pears = world.entity(); + + auto type = world.type() + .add(apples) + .add(pears); + + test_assert(type.has(apples)); + test_assert(type.has(pears)); + + test_assert(type.get(0) == apples); + test_assert(type.get(1) == pears); + + test_expect_abort(); + type.get(2); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/World.cpp b/fggl/ecs2/flecs/test/cpp_api/src/World.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e58a8e921db65ddc23d1f7aaa867baa1c9f23508 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/World.cpp @@ -0,0 +1,1017 @@ +#include <cpp_api.h> + +void World_multi_world_empty() { + flecs::world *w1 = new flecs::world(); + delete w1; + flecs::world *w2 = new flecs::world(); + delete w2; + + test_assert(true); +} + +class FooModule { +public: + FooModule(flecs::world& world) { + world.module<FooModule>(); + } +}; + +typedef struct TestInteropModule { + int dummy; +} TestInteropModule; + +static +void TestInteropModuleImport(ecs_world_t *world) { + ecs_component_desc_t module_desc = {}; + module_desc.entity.name = "TestInteropModule"; + module_desc.size = sizeof(TestInteropModule); + module_desc.alignment = alignof(TestInteropModule); + ecs_entity_t m = ecs_module_init(world, &module_desc); + ecs_set_scope(world, m); + + ecs_component_desc_t desc = {}; + desc.entity.name = "Position"; + desc.entity.symbol = "Position"; + desc.size = sizeof(Position); + desc.alignment = alignof(Position); + ecs_component_init(world, &desc); +} + +namespace test { +namespace interop { + +class module : TestInteropModule { +public: + module(flecs::world& ecs) { + TestInteropModuleImport(ecs.c_ptr()); + + ecs.module<test::interop::module>(); + + ecs.component<Position>("::test::interop::module::Position"); + } +}; + +} +} + +namespace ns { + struct FooComp { + int value; + }; + + struct namespace_module { + namespace_module(flecs::world& ecs) { + ecs.module<namespace_module>(); + + ecs.component<FooComp>(); + + import_count ++; + + ecs.system<FooComp>() + .kind(flecs::OnUpdate) + .each([](flecs::entity entity, FooComp &sc) { + namespace_module::system_invoke_count ++; + }); + } + + static int import_count; + static int system_invoke_count; + }; + + int namespace_module::import_count = 0; + int namespace_module::system_invoke_count = 0; +} + +void World_builtin_components() { + flecs::world ecs; + + test_assert(ecs.component<flecs::Component>() == ecs_id(EcsComponent)); + test_assert(ecs.component<flecs::Identifier>() == ecs_id(EcsIdentifier)); + test_assert(ecs.component<flecs::Type>() == ecs_id(EcsType)); + test_assert(ecs.component<flecs::Trigger>() == ecs_id(EcsTrigger)); + test_assert(ecs.component<flecs::Observer>() == ecs_id(EcsObserver)); + test_assert(ecs.component<flecs::Query>() == ecs_id(EcsQuery)); + test_assert(ecs.component<flecs::RateFilter>() == ecs_id(EcsRateFilter)); + test_assert(ecs.component<flecs::TickSource>() == ecs_id(EcsTickSource)); + test_assert(flecs::Name == EcsName); + test_assert(flecs::Symbol == EcsSymbol); + test_assert(flecs::System == ecs_id(EcsSystem)); +} + +void World_multi_world_component() { + flecs::world w1; + flecs::world w2; + + auto p_1 = w1.component<Position>(); + auto v_1 = w1.component<Velocity>(); + auto v_2 = w2.component<Velocity>(); + auto m_2 = w2.component<Mass>(); + + test_assert(v_1.id() == v_2.id()); + test_assert(p_1.id() != m_2.id()); + test_assert(m_2.id() > v_2.id()); + + auto m_1 = w2.component<Mass>(); + test_assert(m_1.id() == m_2.id()); +} + +namespace A { + struct Comp { + float x; + float y; + }; +} + +void World_multi_world_component_namespace() { + flecs::world *w = new flecs::world(); + auto c = w->component<A::Comp>(); + auto id_1 = c.id(); + delete w; + + w = new flecs::world(); + c = w->component<A::Comp>(); + auto id_2 = c.id(); + + test_assert(id_1 == id_2); + + delete w; +} + +void World_multi_world_module() { + flecs::world world1; + world1.import<ns::namespace_module>(); + + flecs::world world2; + world2.import<ns::namespace_module>(); + + world1.entity().add<ns::FooComp>(); + world2.entity().add<ns::FooComp>(); + + world1.progress(); + test_int(ns::namespace_module::system_invoke_count, 1); + + world2.progress(); + test_int(ns::namespace_module::system_invoke_count, 2); +} + + +void World_type_id() { + flecs::world ecs; + + auto p = ecs.component<Position>(); + + test_assert(p.id() == flecs::type_id<Position>()); +} + +void World_different_comp_same_name() { + flecs::world ecs; + + install_test_abort(); + test_expect_abort(); + + ecs.component<Position>("Position"); + ecs.component<Velocity>("Position"); +} + +void World_reregister_after_reset() { + flecs::world ecs; + + auto p1 = ecs.component<Position>("Position"); + + // Simulate different binary + flecs::_::cpp_type<Position>::reset(); + + auto p2 = ecs.component<Position>("Position"); + + test_assert(p1.id() == p2.id()); +} + +void World_implicit_reregister_after_reset() { + flecs::world ecs; + + ecs.entity().add<Position>(); + + flecs::entity_t p_id_1 = flecs::type_id<Position>(); + + // Simulate different binary + flecs::_::cpp_type<Position>::reset(); + + ecs.entity().add<Position>(); + + flecs::entity_t p_id_2 = flecs::type_id<Position>(); + + test_assert(p_id_1 == p_id_2); +} + +void World_reregister_after_reset_w_namespace() { + flecs::world ecs; + + ecs.component<ns::FooComp>(); + + flecs::entity_t p_id_1 = flecs::type_id<ns::FooComp>(); + + // Simulate different binary + flecs::_::cpp_type<ns::FooComp>::reset(); + + ecs.component<ns::FooComp>(); + + flecs::entity_t p_id_2 = flecs::type_id<ns::FooComp>(); + + test_assert(p_id_1 == p_id_2); +} + +void World_reregister_namespace() { + flecs::world ecs; + + ecs.component<ns::FooComp>(); + + flecs::entity_t p_id_1 = flecs::type_id<ns::FooComp>(); + + ecs.component<ns::FooComp>(); + + flecs::entity_t p_id_2 = flecs::type_id<ns::FooComp>(); + + test_assert(p_id_1 == p_id_2); +} + +void World_reregister_after_reset_different_name() { + flecs::world ecs; + + install_test_abort(); + test_expect_abort(); + + ecs.component<Position>("Position"); + + // Simulate different binary + flecs::_::cpp_type<Position>::reset(); + + ecs.component<Position>("Velocity"); +} + +void World_reimport() { + flecs::world ecs; + + auto m1 = ecs.import<FooModule>(); + + auto m2 = ecs.import<FooModule>(); + + test_assert(m1.id() == m2.id()); +} + +void World_reimport_module_after_reset() { + flecs::world ecs; + + auto m1 = ecs.import<FooModule>(); + + // Simulate different binary + flecs::_::cpp_type<FooModule>::reset(); + + auto m2 = ecs.import<FooModule>(); + + test_assert(m1.id() == m2.id()); +} + +void World_reimport_module_new_world() { + flecs::entity e1; + { + flecs::world ecs; + + e1 = ecs.import<FooModule>(); + } + + { + flecs::world ecs; + + auto e2 = ecs.import<FooModule>(); + + test_assert(e1.id() == e2.id()); + } +} + +void World_reimport_namespaced_module() { + flecs::world ecs; + + test_int(ns::namespace_module::import_count, 0); + + // Import first time, should call module constructor. + ecs.import<ns::namespace_module>(); + + test_int(ns::namespace_module::import_count, 1); + + // Import second time, should not call constructor. + ecs.import<ns::namespace_module>(); + + test_int(ns::namespace_module::import_count, 1); +} + + +void World_c_interop_module() { + flecs::world ecs; + + ecs.import<test::interop::module>(); + + auto e_pos = ecs.lookup("test::interop::module::Position"); + test_assert(e_pos.id() != 0); +} + +void World_c_interop_after_reset() { + flecs::world ecs; + + ecs.import<test::interop::module>(); + + auto e_pos = ecs.lookup("test::interop::module::Position"); + test_assert(e_pos.id() != 0); + + flecs::_::cpp_type<test::interop::module>::reset(); + + ecs.import<test::interop::module>(); +} + +void World_implicit_register_w_new_world() { + { + flecs::world ecs; + + auto e = ecs.entity().set<Position>({10, 20}); + test_assert(e.has<Position>()); + auto *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + } + + { + /* Recreate world, does not reset static state */ + flecs::world ecs; + + auto e = ecs.entity().set<Position>({10, 20}); + test_assert(e.has<Position>()); + auto *p = e.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + } +} + +void World_count() { + flecs::world ecs; + + test_int(ecs.count<Position>(), 0); + + ecs.entity().add<Position>(); + ecs.entity().add<Position>(); + ecs.entity().add<Position>(); + ecs.entity().add<Position>().add<Mass>(); + ecs.entity().add<Position>().add<Mass>(); + ecs.entity().add<Position>().add<Velocity>(); + + test_int(ecs.count<Position>(), 6); +} + +void World_staged_count() { + flecs::world ecs; + + flecs::world stage = ecs.get_stage(0); + + ecs.staging_begin(); + + test_int(stage.count<Position>(), 0); + + ecs.staging_end(); + + ecs.staging_begin(); + + stage.entity().add<Position>(); + stage.entity().add<Position>(); + stage.entity().add<Position>(); + stage.entity().add<Position>().add<Mass>(); + stage.entity().add<Position>().add<Mass>(); + stage.entity().add<Position>().add<Velocity>(); + + test_int(stage.count<Position>(), 0); + + ecs.staging_end(); + + test_int(stage.count<Position>(), 6); +} + +void World_async_stage_add() { + flecs::world ecs; + + ecs.component<Position>(); + + auto e = ecs.entity(); + + flecs::world async = ecs.async_stage(); + e.mut(async).add<Position>(); + test_assert(!e.has<Position>()); + async.merge(); + test_assert(e.has<Position>()); +} + +void World_with_tag() { + flecs::world ecs; + + auto Tag = ecs.entity(); + + ecs.with(Tag, [&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get any contents from the with. + auto self = ecs.component<Self>(); + test_assert(!self.has(Tag)); + + auto q = ecs.query_builder<>().term(Tag).build(); + + int32_t count = 0; + + q.each([&](flecs::entity e) { + test_assert(e.has(Tag)); + + test_bool(e.get([&](const Self& s) { + test_assert(s.value == e); + }), true); + + count ++; + }); + + count ++; +} + +void World_with_tag_type() { + flecs::world ecs; + + struct Tag { }; + + ecs.with<Tag>([&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get any contents from the with. + auto self = ecs.component<Self>(); + test_assert(!self.has<Tag>()); + + auto q = ecs.query_builder<>().term<Tag>().build(); + + int32_t count = 0; + + q.each([&](flecs::entity e) { + test_assert(e.has<Tag>()); + + test_bool(e.get([&](const Self& s) { + test_assert(s.value == e); + }), true); + + count ++; + }); + + count ++; +} + +void World_with_relation() { + flecs::world ecs; + + auto Likes = ecs.entity(); + auto Bob = ecs.entity(); + + ecs.with(Likes, Bob, [&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get any contents from the with. + auto self = ecs.component<Self>(); + test_assert(!self.has(Likes, Bob)); + + auto q = ecs.query_builder<>().term(Likes, Bob).build(); + + int32_t count = 0; + + q.each([&](flecs::entity e) { + test_assert(e.has(Likes, Bob)); + + test_bool(e.get([&](const Self& s) { + test_assert(s.value == e); + }), true); + + count ++; + }); + + count ++; +} + +void World_with_relation_type() { + flecs::world ecs; + + struct Likes { }; + auto Bob = ecs.entity(); + + ecs.with<Likes>(Bob, [&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get any contents from the with. + auto self = ecs.component<Self>(); + test_assert(!self.has<Likes>(Bob)); + + auto q = ecs.query_builder<>().term<Likes>(Bob).build(); + + int32_t count = 0; + + q.each([&](flecs::entity e) { + test_assert(e.has<Likes>(Bob)); + + test_bool(e.get([&](const Self& s) { + test_assert(s.value == e); + }), true); + + count ++; + }); + + count ++; +} + +void World_with_relation_object_type() { + flecs::world ecs; + + struct Likes { }; + struct Bob { }; + + ecs.with<Likes, Bob>([&]{ + auto e1 = ecs.entity(); e1.set<Self>({e1}); + auto e2 = ecs.entity(); e2.set<Self>({e2}); + auto e3 = ecs.entity(); e3.set<Self>({e3}); + }); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not get any contents from the with. + auto self = ecs.component<Self>(); + test_assert(!(self.has<Likes, Bob>())); + + auto q = ecs.query_builder<>().term<Likes, Bob>().build(); + + int32_t count = 0; + + q.each([&](flecs::entity e) { + test_assert((e.has<Likes, Bob>())); + + test_bool(e.get([&](const Self& s) { + test_assert(s.value == e); + }), true); + + count ++; + }); + + count ++; +} + +void World_with_scope() { + flecs::world ecs; + + auto parent = ecs.entity("P"); + + ecs.scope(parent, [&]{ + auto e1 = ecs.entity("C1"); e1.set<Self>({e1}); + auto e2 = ecs.entity("C2"); e2.set<Self>({e2}); + auto e3 = ecs.entity("C3"); e3.set<Self>({e3}); + + // Ensure relative lookups work + test_assert(ecs.lookup("C1") == e1); + test_assert(ecs.lookup("C2") == e2); + test_assert(ecs.lookup("C3") == e3); + + test_assert(parent.lookup("C1") == e1); + test_assert(parent.lookup("C2") == e2); + test_assert(parent.lookup("C3") == e3); + + test_assert(ecs.lookup("::P::C1") == e1); + test_assert(ecs.lookup("::P::C2") == e2); + test_assert(ecs.lookup("::P::C3") == e3); + }); + + test_assert(parent.lookup("C1") != 0); + test_assert(parent.lookup("C2") != 0); + test_assert(parent.lookup("C3") != 0); + + test_assert(ecs.lookup("P::C1") == parent.lookup("C1")); + test_assert(ecs.lookup("P::C2") == parent.lookup("C2")); + test_assert(ecs.lookup("P::C3") == parent.lookup("C3")); + + // Ensures that while Self is (implicitly) registered within the with, it + // does not become a child of the parent. + auto self = ecs.component<Self>(); + test_assert(!self.has(flecs::ChildOf, parent)); + + int count = 0; + auto q = ecs.query_builder<>().term(flecs::ChildOf, parent).build(); + + q.each([&](flecs::entity e) { + test_assert(e.has(flecs::ChildOf, parent)); + + test_bool(e.get([&](const Self& s){ + test_assert(s.value == e); + }), true); + + count ++; + }); + + test_int(count, 3); +} + +void World_with_tag_nested() { + flecs::world ecs; + + auto Tier1 = ecs.entity(); + + ecs.with(Tier1, [&]{ + ecs.entity("Tier2").with([&]{ + ecs.entity("Tier3"); + }); + }); + + auto Tier2 = ecs.lookup("Tier2"); + test_assert(Tier2 != 0); + + auto Tier3 = ecs.lookup("Tier3"); + test_assert(Tier3 != 0); + + test_assert(Tier2.has(Tier1)); + test_assert(Tier3.has(Tier2)); +} + +void World_with_scope_nested() { + flecs::world ecs; + + auto parent = ecs.entity("P"); + + ecs.scope(parent, [&]{ + auto child = ecs.entity("C").scope([&]{ + auto gchild = ecs.entity("GC"); + test_assert(gchild == ecs.lookup("GC")); + test_assert(gchild == ecs.lookup("::P::C::GC")); + }); + + // Ensure relative lookups work + test_assert(ecs.lookup("C") == child); + test_assert(ecs.lookup("::P::C") == child); + test_assert(ecs.lookup("::P::C::GC") != 0); + }); + + test_assert(0 == ecs.lookup("C")); + test_assert(0 == ecs.lookup("GC")); + test_assert(0 == ecs.lookup("C::GC")); + + auto child = ecs.lookup("P::C"); + test_assert(0 != child); + test_assert(child.has(flecs::ChildOf, parent)); + + auto gchild = ecs.lookup("P::C::GC"); + test_assert(0 != gchild); + test_assert(gchild.has(flecs::ChildOf, child)); +} + +void World_recursive_lookup() { + flecs::world ecs; + + auto A = ecs.entity("A"); + auto B = ecs.entity("B"); + + auto P = ecs.entity("P"); + P.scope([&]{ + auto CA = ecs.entity("A"); + test_assert(CA != A); + + test_assert(CA == ecs.lookup("A")); + test_assert(CA == ecs.lookup("P::A")); + test_assert(CA == ecs.lookup("::P::A")); + test_assert(A == ecs.lookup("::A")); + + test_assert(B == ecs.lookup("B")); + test_assert(B == ecs.lookup("::B")); + }); +} + +void World_type_w_tag_name() { + flecs::world ecs; + + auto c = ecs.component<Tag>(); + test_assert(c != flecs::entity()); + test_str(c.path().c_str(), "::Tag"); + test_assert(c != flecs::Tag); +} + +void World_entity_w_tag_name() { + flecs::world ecs; + + auto c = ecs.entity("Tag"); + test_assert(c != flecs::entity()); + test_str(c.path().c_str(), "::Tag"); + test_assert(c != flecs::Tag); +} + +template <typename T> +struct TemplateType { }; + +void World_template_component_name() { + flecs::world ecs; + + auto c = ecs.component<TemplateType<Position>>(); + test_str(c.name().c_str(), "TemplateType<Position>"); + test_str(c.path().c_str(), "::TemplateType<Position>"); +} + +namespace ns { +template <typename T> +struct TemplateType { }; +struct foo { }; +} + +void World_template_component_w_namespace_name() { + flecs::world ecs; + + auto c = ecs.component<ns::TemplateType<Position>>(); + test_str(c.name().c_str(), "TemplateType<Position>"); + test_str(c.path().c_str(), "::ns::TemplateType<Position>"); +} + +void World_template_component_w_namespace_name_and_namespaced_arg() { + flecs::world ecs; + + auto c = ecs.component<ns::TemplateType<ns::foo>>(); + test_str(c.name().c_str(), "TemplateType<ns::foo>"); + test_str(c.path().c_str(), "::ns::TemplateType<ns::foo>"); +} + +namespace foo { +template <typename T> +struct foo { }; +struct bar { }; +} + +void World_template_component_w_same_namespace_name() { + flecs::world ecs; + + auto c = ecs.component<foo::foo<Position>>(); + test_str(c.name().c_str(), "foo<Position>"); + test_str(c.path().c_str(), "::foo::foo<Position>"); +} + +void World_template_component_w_same_namespace_name_and_namespaced_arg() { + flecs::world ecs; + + auto c = ecs.component<foo::foo<foo::bar>>(); + test_str(c.name().c_str(), "foo<foo::bar>"); + test_str(c.path().c_str(), "::foo::foo<foo::bar>"); +} + +void World_entity_as_tag() { + flecs::world ecs; + + auto e = ecs.entity() + .component<Tag>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Tag>(); + test_assert(t.id() != 0); + test_assert(e == t); + + auto e2 = ecs.entity() + .add<Tag>(); + + test_bool(e2.has<Tag>(), true); + test_bool(e2.has(e), true); + + test_str(e.name(), "Tag"); +} + +void World_entity_w_name_as_tag() { + flecs::world ecs; + + auto e = ecs.entity("Foo") + .component<Tag>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Tag>(); + test_assert(t.id() != 0); + test_assert(e == t); + + auto e2 = ecs.entity() + .add<Tag>(); + + test_bool(e2.has<Tag>(), true); + test_bool(e2.has(e), true); + + test_str(e.name(), "Foo"); +} + +void World_type_as_tag() { + flecs::world ecs; + + auto e = ecs.type() + .component<Tag>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Tag>(); + test_assert(t.id() != 0); + test_assert(e.id() == t); + + auto e2 = ecs.entity() + .add<Tag>(); + + test_bool(e2.has<Tag>(), true); + test_bool(e2.has(e), true); + + test_str(t.name(), "Tag"); +} + +void World_entity_as_component() { + flecs::world ecs; + + auto e = ecs.entity() + .component<Position>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Position>(); + test_assert(t.id() != 0); + test_assert(e == t); + + auto e2 = ecs.entity() + .set<Position>({10, 20}); + + test_bool(e2.has<Position>(), true); + test_bool(e2.has(e), true); + + test_str(e.name(), "Position"); +} + +void World_entity_w_name_as_component() { + flecs::world ecs; + + auto e = ecs.entity("Foo") + .component<Position>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Position>(); + test_assert(t.id() != 0); + test_assert(e == t); + + auto e2 = ecs.entity() + .set<Position>({10, 20}); + + test_bool(e2.has<Position>(), true); + test_bool(e2.has(e), true); + + test_str(e.name(), "Foo"); +} + +void World_entity_as_component_2_worlds() { + flecs::world ecs_1; + auto e_1 = ecs_1.entity() + .component<Position>(); + test_assert(e_1.id() != 0); + + flecs::world ecs_2; + auto e_2 = ecs_2.entity() + .component<Position>(); + test_assert(e_2.id() != 0); + + test_assert(e_1 == e_2); + test_assert(e_1 == ecs_1.component<Position>()); + test_assert(e_2 == ecs_2.component<Position>()); +} + +struct Parent { + struct Child { }; +}; + +void World_entity_as_namespaced_component_2_worlds() { + flecs::world ecs_1; + auto e_1 = ecs_1.entity() + .component<Parent>(); + test_assert(e_1.id() != 0); + + auto e_1_1 = ecs_1.entity() + .component<Parent::Child>(); + test_assert(e_1_1.id() != 0); + + flecs::world ecs_2; + auto e_2 = ecs_2.entity() + .component<Parent>(); + test_assert(e_2.id() != 0); + + auto e_2_1 = ecs_2.entity() + .component<Parent::Child>(); + test_assert(e_2_1.id() != 0); + + test_assert(e_1 == e_2); + test_assert(e_1 == ecs_1.component<Parent>()); + test_assert(e_2 == ecs_2.component<Parent>()); + + test_assert(e_1_1 == e_2_1); + test_assert(e_1_1 == ecs_1.component<Parent::Child>()); + test_assert(e_2_1 == ecs_2.component<Parent::Child>()); +} + +void World_entity_as_component_2_worlds_implicit_namespaced() { + flecs::world ecs_1; + auto e_1 = ecs_1.entity() + .component<Parent>(); + test_assert(e_1.id() != 0); + + ecs_1.entity().add<Parent::Child>(); + + flecs::world ecs_2; + auto e_2 = ecs_2.entity() + .component<Parent>(); + test_assert(e_2.id() != 0); + + ecs_2.entity().add<Parent::Child>(); + + test_assert(e_1 == e_2); + test_assert(e_1 == ecs_1.component<Parent>()); + test_assert(e_2 == ecs_2.component<Parent>()); + + test_assert(ecs_1.component<Parent::Child>() == + ecs_2.component<Parent::Child>()); +} + +void World_type_as_component() { + flecs::world ecs; + + auto e = ecs.type() + .component<Position>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Position>(); + test_assert(t.id() != 0); + test_assert(e.id() == t); + + auto e2 = ecs.entity() + .set<Position>({10, 20}); + + test_bool(e2.has<Position>(), true); + test_bool(e2.has(e), true); + + test_str(t.name(), "Position"); +} + +void World_type_w_name_as_component() { + flecs::world ecs; + + auto e = ecs.type("Foo") + .component<Position>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Position>(); + test_assert(t.id() != 0); + test_assert(e.id() == t); + + auto e2 = ecs.entity() + .set<Position>({10, 20}); + + test_bool(e2.has<Position>(), true); + test_bool(e2.has(e), true); + + test_str(t.name(), "Foo"); +} + +struct PositionDerived : Position { + PositionDerived() { } + PositionDerived(float x, float y) : Position{x, y} { } +}; + +void World_component_as_component() { + flecs::world ecs; + + auto e = ecs.component<Position>() + .component<PositionDerived>(); + test_assert(e.id() != 0); + + auto t = ecs.component<Position>(); + test_assert(t.id() != 0); + test_assert(e == t); + + auto e2 = ecs.entity() + .set<PositionDerived>({10, 20}); + + test_bool(e2.has<Position>(), true); + test_bool(e2.has<PositionDerived>(), true); + + const Position *p = e2.get<Position>(); + test_assert(p != NULL); + test_int(p->x, 10); + test_int(p->y, 20); + + test_str(e.name(), "Position"); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/WorldFactory.cpp b/fggl/ecs2/flecs/test/cpp_api/src/WorldFactory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e1c1e4b2baa6dd73b591cb8cd97fa803a030755b --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/WorldFactory.cpp @@ -0,0 +1,247 @@ +#include <cpp_api.h> + +void WorldFactory_entity() { + flecs::world ecs; + + auto e = ecs.entity(); + test_assert(e.id() != 0); +} + +void WorldFactory_entity_w_name() { + flecs::world ecs; + + auto e = ecs.entity("MyName"); + test_assert(e.id() != 0); + test_str(e.name().c_str(), "MyName"); +} + +void WorldFactory_entity_w_id() { + flecs::world ecs; + + auto e = ecs.entity(100); + test_assert(e.id() == 100); +} + +void WorldFactory_prefab() { + flecs::world ecs; + + auto e = ecs.prefab(); + test_assert(e.id() != 0); + test_assert(e.has(flecs::Prefab)); +} + +void WorldFactory_prefab_w_name() { + flecs::world ecs; + + auto e = ecs.prefab("MyName"); + test_assert(e.id() != 0); + test_assert(e.has(flecs::Prefab)); + test_str(e.name().c_str(), "MyName"); +} + +void WorldFactory_type() { + flecs::world ecs; + + auto t = ecs.type() + .add<Position>() + .add<Velocity>(); + + auto e = ecs.entity().add(t); + + test_assert(e.id() != 0); + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); +} + +void WorldFactory_type_w_name() { + flecs::world ecs; + + auto t = ecs.type("MyName") + .add<Position>() + .add<Velocity>(); + + auto e = ecs.entity().add(t); + + test_assert(e.id() != 0); + test_assert(e.has<Position>()); + test_assert(e.has<Velocity>()); + + test_assert(ecs.lookup("MyName").id() != 0); +} + +void WorldFactory_system() { + flecs::world ecs; + + auto s = ecs.system<Position, const Velocity>() + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + test_assert(s.id() != 0); + + auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.progress(); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void WorldFactory_system_w_name() { + flecs::world ecs; + + auto s = ecs.system<Position, const Velocity>("MySystem") + .each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + test_assert(s.id() != 0); + test_str(s.name().c_str(), "MySystem"); + + auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.progress(); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void WorldFactory_system_w_expr() { + flecs::world ecs; + + ecs.component<Position>(); + ecs.component<Velocity>(); + + auto s = ecs.system<>("MySystem", "Position, [in] Velocity") + .iter([](flecs::iter it) { + flecs::column<Position> p(it, 1); + flecs::column<const Velocity> v(it, 2); + + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + test_assert(s.id() != 0); + test_str(s.name().c_str(), "MySystem"); + + auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + ecs.progress(); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void WorldFactory_query() { + flecs::world ecs; + + auto q = ecs.query<Position, const Velocity>(); + + auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + q.each([](flecs::entity e, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + }); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void WorldFactory_query_w_expr() { + flecs::world ecs; + + ecs.component<Position>(); + ecs.component<Velocity>(); + + auto q = ecs.query<>("Position, [in] Velocity"); + + auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + q.iter([](flecs::iter it) { + flecs::column<Position> p(it, 1); + flecs::column<const Velocity> v(it, 2); + + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + } + }); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +void WorldFactory_snapshot() { + flecs::world ecs; + + ecs.component<Position>(); + ecs.component<Velocity>(); + + auto e = ecs.entity() + .set<Position>({10, 20}) + .set<Velocity>({1, 2}); + + auto s = ecs.snapshot(); + + e.set<Position>({11, 22}); + + s.restore(); + + const Position *p = e.get<Position>(); + test_int(p->x, 11); + test_int(p->y, 22); +} + +class MyModule { +public: + MyModule(flecs::world& ecs) { + ecs.module<MyModule>(); + ecs.component<Position>(); + } +}; + +void WorldFactory_module() { + flecs::world ecs; + + ecs.import<MyModule>(); + + auto p = ecs.lookup("MyModule::Position"); + test_assert(p.id() != 0); +} + +class MyNamedModule { +public: + MyNamedModule(flecs::world& ecs) { + ecs.module<MyNamedModule>("ModuleName"); + ecs.component<Position>(); + } +}; + +void WorldFactory_module_w_name() { + flecs::world ecs; + + ecs.import<MyNamedModule>(); + + auto p = ecs.lookup("ModuleName::Position"); + test_assert(p.id() != 0); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/main.cpp b/fggl/ecs2/flecs/test/cpp_api/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c7ded305c5dd4fce222a5738a18d34ad07c4cd17 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/main.cpp @@ -0,0 +1,3522 @@ + +/* A friendly warning from bake.test + * ---------------------------------------------------------------------------- + * This file is generated. To add/remove testcases modify the 'project.json' of + * the test project. ANY CHANGE TO THIS FILE IS LOST AFTER (RE)BUILDING! + * ---------------------------------------------------------------------------- + */ + +#include <cpp_api.h> + +// Testsuite 'Entity' +void Entity_new(void); +void Entity_new_named(void); +void Entity_new_named_from_scope(void); +void Entity_new_nested_named_from_scope(void); +void Entity_new_nested_named_from_nested_scope(void); +void Entity_new_add(void); +void Entity_new_add_2(void); +void Entity_new_set(void); +void Entity_new_set_2(void); +void Entity_add(void); +void Entity_add_2(void); +void Entity_add_entity(void); +void Entity_add_childof(void); +void Entity_add_instanceof(void); +void Entity_remove(void); +void Entity_remove_2(void); +void Entity_remove_entity(void); +void Entity_remove_childof(void); +void Entity_remove_instanceof(void); +void Entity_set(void); +void Entity_set_2(void); +void Entity_emplace(void); +void Entity_emplace_2(void); +void Entity_emplace_after_add(void); +void Entity_emplace_after_add_pair(void); +void Entity_emplace_w_self_ctor(void); +void Entity_replace(void); +void Entity_get_generic(void); +void Entity_get_mut_generic(void); +void Entity_get_generic_w_id(void); +void Entity_get_mut_generic_w_id(void); +void Entity_add_role(void); +void Entity_remove_role(void); +void Entity_has_role(void); +void Entity_pair_role(void); +void Entity_equals(void); +void Entity_compare_0(void); +void Entity_compare_id_t(void); +void Entity_compare_id(void); +void Entity_compare_literal(void); +void Entity_greater_than(void); +void Entity_less_than(void); +void Entity_not_0_or_1(void); +void Entity_not_true_or_false(void); +void Entity_has_childof(void); +void Entity_has_instanceof(void); +void Entity_has_instanceof_indirect(void); +void Entity_null_string(void); +void Entity_set_name(void); +void Entity_change_name(void); +void Entity_delete(void); +void Entity_clear(void); +void Entity_foce_owned(void); +void Entity_force_owned_2(void); +void Entity_force_owned_nested(void); +void Entity_force_owned_type(void); +void Entity_force_owned_type_w_pair(void); +void Entity_tag_has_size_zero(void); +void Entity_get_null_name(void); +void Entity_get_parent(void); +void Entity_is_component_enabled(void); +void Entity_is_enabled_component_enabled(void); +void Entity_is_disabled_component_enabled(void); +void Entity_get_type(void); +void Entity_get_nonempty_type(void); +void Entity_set_no_copy(void); +void Entity_set_copy(void); +void Entity_set_deduced(void); +void Entity_add_owned(void); +void Entity_set_owned(void); +void Entity_implicit_name_to_char(void); +void Entity_implicit_path_to_char(void); +void Entity_implicit_type_str_to_char(void); +void Entity_entity_to_entity_view(void); +void Entity_entity_view_to_entity_world(void); +void Entity_entity_view_to_entity_stage(void); +void Entity_create_entity_view_from_stage(void); +void Entity_set_template(void); +void Entity_get_1_component_w_callback(void); +void Entity_get_2_components_w_callback(void); +void Entity_set_1_component_w_callback(void); +void Entity_set_2_components_w_callback(void); +void Entity_set_3_components_w_callback(void); +void Entity_defer_set_1_component(void); +void Entity_defer_set_2_components(void); +void Entity_defer_set_3_components(void); +void Entity_set_2_w_on_set(void); +void Entity_defer_set_2_w_on_set(void); +void Entity_set_2_after_fluent(void); +void Entity_set_2_before_fluent(void); +void Entity_set_2_after_set_1(void); +void Entity_set_2_after_set_2(void); +void Entity_with_self(void); +void Entity_with_relation_type_self(void); +void Entity_with_relation_self(void); +void Entity_with_self_w_name(void); +void Entity_with_self_nested(void); +void Entity_with_after_builder_method(void); +void Entity_with_before_builder_method(void); +void Entity_with_scope(void); +void Entity_with_scope_nested(void); +void Entity_with_scope_nested_same_name_as_parent(void); +void Entity_scope_after_builder_method(void); +void Entity_scope_before_builder_method(void); +void Entity_no_recursive_lookup(void); +void Entity_defer_new_w_name(void); +void Entity_defer_new_w_nested_name(void); +void Entity_defer_new_w_scope_name(void); +void Entity_defer_new_w_scope_nested_name(void); +void Entity_defer_new_w_deferred_scope_nested_name(void); +void Entity_defer_new_w_scope(void); +void Entity_defer_new_w_with(void); +void Entity_defer_new_w_name_scope_with(void); +void Entity_defer_w_with_implicit_component(void); +void Entity_entity_id_str(void); +void Entity_pair_id_str(void); +void Entity_role_id_str(void); +void Entity_id_str_from_entity_view(void); +void Entity_id_str_from_entity(void); +void Entity_null_entity(void); +void Entity_null_entity_w_world(void); +void Entity_null_entity_w_0(void); +void Entity_null_entity_w_world_w_0(void); +void Entity_entity_view_null_entity(void); +void Entity_entity_view_null_entity_w_world(void); +void Entity_entity_view_null_entity_w_0(void); +void Entity_entity_view_null_entity_w_world_w_0(void); +void Entity_is_wildcard(void); +void Entity_has_id_t(void); +void Entity_has_pair_id_t(void); +void Entity_has_pair_id_t_w_type(void); +void Entity_has_id(void); +void Entity_has_pair_id(void); +void Entity_has_pair_id_w_type(void); +void Entity_has_wildcard_id(void); +void Entity_has_wildcard_pair_id(void); +void Entity_owns_id_t(void); +void Entity_owns_pair_id_t(void); +void Entity_owns_pair_id_t_w_type(void); +void Entity_owns_id(void); +void Entity_owns_pair_id(void); +void Entity_owns_pair_id_w_type(void); +void Entity_owns_wildcard_id(void); +void Entity_owns_wildcard_pair(void); +void Entity_id_from_world(void); +void Entity_id_pair_from_world(void); +void Entity_id_default_from_world(void); +void Entity_is_a(void); +void Entity_is_a_w_type(void); +void Entity_child_of(void); +void Entity_child_of_w_type(void); +void Entity_id_get_entity(void); +void Entity_id_get_invalid_entity(void); + +// Testsuite 'Pairs' +void Pairs_add_component_pair(void); +void Pairs_add_tag_pair(void); +void Pairs_add_tag_pair_to_tag(void); +void Pairs_remove_component_pair(void); +void Pairs_remove_tag_pair(void); +void Pairs_remove_tag_pair_to_tag(void); +void Pairs_set_component_pair(void); +void Pairs_set_tag_pair(void); +void Pairs_system_1_pair_instance(void); +void Pairs_system_2_pair_instances(void); +void Pairs_override_pair(void); +void Pairs_override_tag_pair(void); +void Pairs_get_mut_pair(void); +void Pairs_get_mut_pair_existing(void); +void Pairs_get_mut_pair_tag(void); +void Pairs_get_mut_pair_tag_existing(void); +void Pairs_type_w_pair(void); +void Pairs_type_w_pair_tag(void); +void Pairs_type_w_pair_tags(void); +void Pairs_type_w_tag_pair(void); +void Pairs_override_pair_w_type(void); +void Pairs_override_pair_w_type_tag(void); +void Pairs_override_tag_pair_w_type(void); +void Pairs_get_relation_from_id(void); +void Pairs_get_w_object_from_id(void); +void Pairs_get_recycled_relation_from_id(void); +void Pairs_get_recycled_object_from_id(void); +void Pairs_each(void); +void Pairs_each_pair(void); +void Pairs_each_pair_by_type(void); +void Pairs_each_pair_w_childof(void); +void Pairs_each_pair_w_recycled_rel(void); +void Pairs_each_pair_w_recycled_obj(void); +void Pairs_match_pair(void); +void Pairs_match_pair_obj_wildcard(void); +void Pairs_match_pair_rel_wildcard(void); +void Pairs_match_pair_both_wildcard(void); +void Pairs_has_tag_w_object(void); +void Pairs_has_w_object_tag(void); +void Pairs_add_pair_type(void); +void Pairs_remove_pair_type(void); +void Pairs_set_pair_type(void); +void Pairs_has_pair_type(void); +void Pairs_get_1_pair_arg(void); +void Pairs_get_2_pair_arg(void); +void Pairs_set_1_pair_arg(void); +void Pairs_set_2_pair_arg(void); +void Pairs_get_inline_pair_type(void); +void Pairs_set_inline_pair_type(void); +void Pairs_get_pair_type_object(void); +void Pairs_set_pair_type_object(void); +void Pairs_set_get_w_object_variants(void); + +// Testsuite 'Switch' +void Switch_add_case(void); +void Switch_get_case(void); +void Switch_system_w_case(void); +void Switch_system_w_switch(void); +void Switch_add_case_w_type(void); +void Switch_add_switch_w_type(void); +void Switch_add_switch_w_type_component_first(void); +void Switch_add_remove_switch_w_type(void); + +// Testsuite 'Paths' +void Paths_name(void); +void Paths_path_depth_1(void); +void Paths_path_depth_2(void); +void Paths_entity_lookup_name(void); +void Paths_entity_lookup_depth_1(void); +void Paths_entity_lookup_depth_2(void); +void Paths_alias_component(void); +void Paths_alias_scoped_component(void); +void Paths_alias_scoped_component_w_name(void); +void Paths_alias_entity(void); +void Paths_alias_entity_by_name(void); +void Paths_alias_entity_by_scoped_name(void); + +// Testsuite 'Type' +void Type_add_2(void); +void Type_add_instanceof(void); +void Type_add_childof(void); +void Type_1_component(void); +void Type_2_component(void); +void Type_1_component_signature(void); +void Type_2_component_signature(void); +void Type_type_no_name(void); +void Type_null_args(void); +void Type_has_type(void); +void Type_has_entity(void); +void Type_has_pair_type(void); +void Type_has_pair_entity(void); +void Type_get(void); +void Type_get_out_of_range(void); + +// Testsuite 'System' +void System_action(void); +void System_action_const(void); +void System_action_shared(void); +void System_action_optional(void); +void System_each(void); +void System_each_const(void); +void System_each_shared(void); +void System_each_optional(void); +void System_signature(void); +void System_signature_const(void); +void System_signature_shared(void); +void System_signature_optional(void); +void System_copy_name_on_create(void); +void System_nested_system(void); +void System_empty_signature(void); +void System_action_tag(void); +void System_iter_tag(void); +void System_each_tag(void); +void System_system_from_id(void); +void System_set_interval(void); +void System_order_by_type(void); +void System_order_by_id(void); +void System_order_by_type_after_create(void); +void System_order_by_id_after_create(void); +void System_get_query(void); +void System_add_from_each(void); +void System_delete_from_each(void); +void System_add_from_each_world_handle(void); +void System_new_from_each(void); +void System_add_from_iter(void); +void System_delete_from_iter(void); +void System_add_from_iter_world_handle(void); +void System_new_from_iter(void); +void System_each_w_mut_children_it(void); +void System_readonly_children_iter(void); +void System_rate_filter(void); +void System_update_rate_filter(void); +void System_default_ctor(void); +void System_test_auto_defer_each(void); +void System_test_auto_defer_iter(void); +void System_custom_pipeline(void); +void System_system_w_self(void); + +// Testsuite 'Trigger' +void Trigger_on_add(void); +void Trigger_on_remove(void); +void Trigger_on_add_tag_action(void); +void Trigger_on_add_tag_iter(void); +void Trigger_on_add_tag_each(void); +void Trigger_trigger_w_self(void); + +// Testsuite 'Query' +void Query_action(void); +void Query_action_const(void); +void Query_action_shared(void); +void Query_action_optional(void); +void Query_each(void); +void Query_each_const(void); +void Query_each_shared(void); +void Query_each_optional(void); +void Query_signature(void); +void Query_signature_const(void); +void Query_signature_shared(void); +void Query_signature_optional(void); +void Query_subquery(void); +void Query_subquery_w_expr(void); +void Query_query_single_pair(void); +void Query_tag_w_each(void); +void Query_shared_tag_w_each(void); +void Query_sort_by(void); +void Query_changed(void); +void Query_orphaned(void); +void Query_default_ctor(void); +void Query_expr_w_template(void); +void Query_query_type_w_template(void); +void Query_compare_term_id(void); +void Query_test_no_defer_each(void); +void Query_test_no_defer_iter(void); +void Query_inspect_terms(void); +void Query_inspect_terms_w_each(void); +void Query_comp_to_str(void); +void Query_pair_to_str(void); +void Query_oper_not_to_str(void); +void Query_oper_optional_to_str(void); +void Query_oper_or_to_str(void); +void Query_each_pair_type(void); +void Query_iter_pair_type(void); +void Query_term_pair_type(void); +void Query_each_no_entity_1_comp(void); +void Query_each_no_entity_2_comps(void); +void Query_iter_no_comps_1_comp(void); +void Query_iter_no_comps_2_comps(void); +void Query_iter_no_comps_no_comps(void); +void Query_each_pair_object(void); +void Query_iter_pair_object(void); +void Query_iter_query_in_system(void); +void Query_iter_type(void); + +// Testsuite 'QueryBuilder' +void QueryBuilder_builder_assign_same_type(void); +void QueryBuilder_builder_assign_from_empty(void); +void QueryBuilder_builder_assign_to_empty(void); +void QueryBuilder_builder_build(void); +void QueryBuilder_builder_build_to_auto(void); +void QueryBuilder_builder_build_n_statements(void); +void QueryBuilder_1_type(void); +void QueryBuilder_add_1_type(void); +void QueryBuilder_add_2_types(void); +void QueryBuilder_add_1_type_w_1_type(void); +void QueryBuilder_add_2_types_w_1_type(void); +void QueryBuilder_add_pair(void); +void QueryBuilder_add_not(void); +void QueryBuilder_add_or(void); +void QueryBuilder_add_optional(void); +void QueryBuilder_ptr_type(void); +void QueryBuilder_const_type(void); +void QueryBuilder_string_term(void); +void QueryBuilder_singleton_term(void); +void QueryBuilder_isa_superset_term(void); +void QueryBuilder_isa_self_superset_term(void); +void QueryBuilder_childof_superset_term(void); +void QueryBuilder_childof_self_superset_term(void); +void QueryBuilder_isa_superset_term_w_each(void); +void QueryBuilder_isa_self_superset_term_w_each(void); +void QueryBuilder_childof_superset_term_w_each(void); +void QueryBuilder_childof_self_superset_term_w_each(void); +void QueryBuilder_isa_superset_shortcut(void); +void QueryBuilder_isa_superset_shortcut_w_self(void); +void QueryBuilder_childof_superset_shortcut(void); +void QueryBuilder_childof_superset_shortcut_w_self(void); +void QueryBuilder_isa_superset_max_depth_1(void); +void QueryBuilder_isa_superset_max_depth_2(void); +void QueryBuilder_isa_superset_min_depth_2(void); +void QueryBuilder_isa_superset_min_depth_2_max_depth_3(void); +void QueryBuilder_role(void); +void QueryBuilder_relation(void); +void QueryBuilder_relation_w_object_wildcard(void); +void QueryBuilder_relation_w_predicate_wildcard(void); +void QueryBuilder_add_pair_w_rel_type(void); +void QueryBuilder_template_term(void); +void QueryBuilder_explicit_subject_w_id(void); +void QueryBuilder_explicit_subject_w_type(void); +void QueryBuilder_explicit_object_w_id(void); +void QueryBuilder_explicit_object_w_type(void); +void QueryBuilder_explicit_term(void); +void QueryBuilder_explicit_term_w_type(void); +void QueryBuilder_explicit_term_w_pair_type(void); +void QueryBuilder_explicit_term_w_id(void); +void QueryBuilder_explicit_term_w_pair_id(void); +void QueryBuilder_1_term_to_empty(void); +void QueryBuilder_2_subsequent_args(void); +void QueryBuilder_optional_tag_is_set(void); +void QueryBuilder_10_terms(void); +void QueryBuilder_20_terms(void); + +// Testsuite 'FilterBuilder' +void FilterBuilder_builder_assign_same_type(void); +void FilterBuilder_builder_assign_from_empty(void); +void FilterBuilder_builder_assign_to_empty(void); +void FilterBuilder_builder_build(void); +void FilterBuilder_builder_build_to_auto(void); +void FilterBuilder_builder_build_n_statements(void); +void FilterBuilder_1_type(void); +void FilterBuilder_add_1_type(void); +void FilterBuilder_add_2_types(void); +void FilterBuilder_add_1_type_w_1_type(void); +void FilterBuilder_add_2_types_w_1_type(void); +void FilterBuilder_add_pair(void); +void FilterBuilder_add_not(void); +void FilterBuilder_add_or(void); +void FilterBuilder_add_optional(void); +void FilterBuilder_ptr_type(void); +void FilterBuilder_const_type(void); +void FilterBuilder_string_term(void); +void FilterBuilder_singleton_term(void); +void FilterBuilder_isa_superset_term(void); +void FilterBuilder_isa_self_superset_term(void); +void FilterBuilder_childof_superset_term(void); +void FilterBuilder_childof_self_superset_term(void); +void FilterBuilder_isa_superset_term_w_each(void); +void FilterBuilder_isa_self_superset_term_w_each(void); +void FilterBuilder_childof_superset_term_w_each(void); +void FilterBuilder_childof_self_superset_term_w_each(void); +void FilterBuilder_isa_superset_shortcut(void); +void FilterBuilder_isa_superset_shortcut_w_self(void); +void FilterBuilder_childof_superset_shortcut(void); +void FilterBuilder_childof_superset_shortcut_w_self(void); +void FilterBuilder_isa_superset_max_depth_1(void); +void FilterBuilder_isa_superset_max_depth_2(void); +void FilterBuilder_isa_superset_min_depth_2(void); +void FilterBuilder_isa_superset_min_depth_2_max_depth_3(void); +void FilterBuilder_relation(void); +void FilterBuilder_relation_w_object_wildcard(void); +void FilterBuilder_relation_w_predicate_wildcard(void); +void FilterBuilder_add_pair_w_rel_type(void); +void FilterBuilder_template_term(void); +void FilterBuilder_explicit_subject_w_id(void); +void FilterBuilder_explicit_subject_w_type(void); +void FilterBuilder_explicit_object_w_id(void); +void FilterBuilder_explicit_object_w_type(void); +void FilterBuilder_explicit_term(void); +void FilterBuilder_explicit_term_w_type(void); +void FilterBuilder_explicit_term_w_pair_type(void); +void FilterBuilder_explicit_term_w_id(void); +void FilterBuilder_explicit_term_w_pair_id(void); +void FilterBuilder_1_term_to_empty(void); +void FilterBuilder_2_subsequent_args(void); +void FilterBuilder_filter_as_arg(void); +void FilterBuilder_filter_as_move_arg(void); +void FilterBuilder_filter_as_return(void); +void FilterBuilder_filter_copy(void); +void FilterBuilder_world_each_filter_1_component(void); +void FilterBuilder_world_each_filter_2_components(void); +void FilterBuilder_world_each_filter_1_component_no_entity(void); +void FilterBuilder_world_each_filter_2_components_no_entity(void); +void FilterBuilder_10_terms(void); +void FilterBuilder_20_terms(void); +void FilterBuilder_term_after_arg(void); + +// Testsuite 'SystemBuilder' +void SystemBuilder_builder_assign_same_type(void); +void SystemBuilder_builder_assign_from_empty(void); +void SystemBuilder_builder_build_to_auto(void); +void SystemBuilder_builder_build_n_statements(void); +void SystemBuilder_1_type(void); +void SystemBuilder_add_1_type(void); +void SystemBuilder_add_2_types(void); +void SystemBuilder_add_1_type_w_1_type(void); +void SystemBuilder_add_2_types_w_1_type(void); +void SystemBuilder_add_pair(void); +void SystemBuilder_add_not(void); +void SystemBuilder_add_or(void); +void SystemBuilder_add_optional(void); +void SystemBuilder_ptr_type(void); +void SystemBuilder_const_type(void); +void SystemBuilder_string_term(void); +void SystemBuilder_singleton_term(void); +void SystemBuilder_10_terms(void); +void SystemBuilder_20_terms(void); + +// Testsuite 'Observer' +void Observer_2_terms_on_add(void); +void Observer_2_terms_on_remove(void); +void Observer_2_terms_on_set(void); +void Observer_2_terms_un_set(void); +void Observer_observer_w_self(void); +void Observer_10_terms(void); +void Observer_20_terms(void); + +// Testsuite 'Filter' +void Filter_term_each_component(void); +void Filter_term_each_tag(void); +void Filter_term_each_id(void); +void Filter_term_each_pair_type(void); +void Filter_term_each_pair_id(void); +void Filter_term_each_pair_relation_wildcard(void); +void Filter_term_each_pair_object_wildcard(void); + +// Testsuite 'ComponentLifecycle' +void ComponentLifecycle_ctor_on_add(void); +void ComponentLifecycle_dtor_on_remove(void); +void ComponentLifecycle_move_on_add(void); +void ComponentLifecycle_move_on_remove(void); +void ComponentLifecycle_copy_on_set(void); +void ComponentLifecycle_copy_on_override(void); +void ComponentLifecycle_non_pod_add(void); +void ComponentLifecycle_non_pod_remove(void); +void ComponentLifecycle_non_pod_set(void); +void ComponentLifecycle_non_pod_override(void); +void ComponentLifecycle_get_mut_new(void); +void ComponentLifecycle_get_mut_existing(void); +void ComponentLifecycle_pod_component(void); +void ComponentLifecycle_relocatable_component(void); +void ComponentLifecycle_implicit_component(void); +void ComponentLifecycle_implicit_after_query(void); +void ComponentLifecycle_deleted_copy(void); +void ComponentLifecycle_no_default_ctor_emplace(void); +void ComponentLifecycle_default_init(void); +void ComponentLifecycle_no_default_ctor_add(void); +void ComponentLifecycle_no_default_ctor_add_relation(void); +void ComponentLifecycle_no_default_ctor_add_w_object(void); +void ComponentLifecycle_no_default_ctor_set(void); +void ComponentLifecycle_no_copy_ctor(void); +void ComponentLifecycle_no_move_ctor(void); +void ComponentLifecycle_no_copy_assign(void); +void ComponentLifecycle_no_move_assign(void); +void ComponentLifecycle_no_copy(void); +void ComponentLifecycle_no_move(void); +void ComponentLifecycle_no_dtor(void); +void ComponentLifecycle_flecs_ctor(void); +void ComponentLifecycle_flecs_ctor_w_default_ctor(void); +void ComponentLifecycle_default_ctor_w_value_ctor(void); +void ComponentLifecycle_flecs_ctor_w_value_ctor(void); +void ComponentLifecycle_no_default_ctor_move_ctor_on_set(void); +void ComponentLifecycle_emplace_w_ctor(void); +void ComponentLifecycle_emplace_no_default_ctor(void); +void ComponentLifecycle_emplace_existing(void); +void ComponentLifecycle_emplace_singleton(void); +void ComponentLifecycle_dtor_w_non_trivial_implicit_move(void); +void ComponentLifecycle_dtor_w_non_trivial_explicit_move(void); + +// Testsuite 'Refs' +void Refs_get_ref(void); +void Refs_ref_after_add(void); +void Refs_ref_after_remove(void); +void Refs_ref_after_set(void); +void Refs_ref_before_set(void); + +// Testsuite 'Module' +void Module_import(void); +void Module_lookup_from_scope(void); +void Module_nested_module(void); +void Module_nested_type_module(void); +void Module_module_type_w_explicit_name(void); +void Module_component_redefinition_outside_module(void); +void Module_module_tag_on_namespace(void); +void Module_dtor_on_fini(void); + +// Testsuite 'ImplicitComponents' +void ImplicitComponents_add(void); +void ImplicitComponents_remove(void); +void ImplicitComponents_has(void); +void ImplicitComponents_set(void); +void ImplicitComponents_get(void); +void ImplicitComponents_add_pair(void); +void ImplicitComponents_remove_pair(void); +void ImplicitComponents_module(void); +void ImplicitComponents_system(void); +void ImplicitComponents_system_optional(void); +void ImplicitComponents_system_const(void); +void ImplicitComponents_query(void); +void ImplicitComponents_implicit_name(void); +void ImplicitComponents_reinit(void); +void ImplicitComponents_reinit_scoped(void); +void ImplicitComponents_reinit_w_lifecycle(void); +void ImplicitComponents_first_use_in_system(void); +void ImplicitComponents_first_use_tag_in_system(void); +void ImplicitComponents_use_const(void); +void ImplicitComponents_use_const_w_stage(void); +void ImplicitComponents_use_const_w_threads(void); +void ImplicitComponents_implicit_base(void); +void ImplicitComponents_implicit_const(void); +void ImplicitComponents_implicit_ref(void); +void ImplicitComponents_implicit_ptr(void); +void ImplicitComponents_implicit_const_ref(void); + +// Testsuite 'Snapshot' +void Snapshot_simple_snapshot(void); +void Snapshot_snapshot_iter(void); + +// Testsuite 'WorldFactory' +void WorldFactory_entity(void); +void WorldFactory_entity_w_name(void); +void WorldFactory_entity_w_id(void); +void WorldFactory_prefab(void); +void WorldFactory_prefab_w_name(void); +void WorldFactory_type(void); +void WorldFactory_type_w_name(void); +void WorldFactory_system(void); +void WorldFactory_system_w_name(void); +void WorldFactory_system_w_expr(void); +void WorldFactory_query(void); +void WorldFactory_query_w_expr(void); +void WorldFactory_snapshot(void); +void WorldFactory_module(void); +void WorldFactory_module_w_name(void); + +// Testsuite 'World' +void World_builtin_components(void); +void World_multi_world_empty(void); +void World_multi_world_component(void); +void World_multi_world_component_namespace(void); +void World_multi_world_module(void); +void World_type_id(void); +void World_different_comp_same_name(void); +void World_reregister_after_reset(void); +void World_reregister_after_reset_w_namespace(void); +void World_reregister_namespace(void); +void World_implicit_reregister_after_reset(void); +void World_reregister_after_reset_different_name(void); +void World_reimport(void); +void World_reimport_module_after_reset(void); +void World_reimport_module_new_world(void); +void World_reimport_namespaced_module(void); +void World_c_interop_module(void); +void World_c_interop_after_reset(void); +void World_implicit_register_w_new_world(void); +void World_count(void); +void World_staged_count(void); +void World_async_stage_add(void); +void World_with_tag(void); +void World_with_tag_type(void); +void World_with_relation(void); +void World_with_relation_type(void); +void World_with_relation_object_type(void); +void World_with_tag_nested(void); +void World_with_scope(void); +void World_with_scope_nested(void); +void World_recursive_lookup(void); +void World_type_w_tag_name(void); +void World_entity_w_tag_name(void); +void World_template_component_name(void); +void World_template_component_w_namespace_name(void); +void World_template_component_w_same_namespace_name(void); +void World_template_component_w_namespace_name_and_namespaced_arg(void); +void World_template_component_w_same_namespace_name_and_namespaced_arg(void); +void World_entity_as_tag(void); +void World_entity_w_name_as_tag(void); +void World_type_as_tag(void); +void World_entity_as_component(void); +void World_entity_w_name_as_component(void); +void World_entity_as_component_2_worlds(void); +void World_entity_as_namespaced_component_2_worlds(void); +void World_entity_as_component_2_worlds_implicit_namespaced(void); +void World_type_as_component(void); +void World_type_w_name_as_component(void); +void World_component_as_component(void); + +// Testsuite 'Singleton' +void Singleton_set_get_singleton(void); +void Singleton_get_mut_singleton(void); +void Singleton_emplace_singleton(void); +void Singleton_modified_singleton(void); +void Singleton_patch_singleton(void); +void Singleton_add_singleton(void); +void Singleton_remove_singleton(void); +void Singleton_has_singleton(void); +void Singleton_singleton_system(void); +void Singleton_get_singleton(void); +void Singleton_type_id_from_world(void); + +// Testsuite 'Misc' +void Misc_setup(void); +void Misc_string_compare_w_char_ptr(void); +void Misc_string_compare_w_char_ptr_length_diff(void); +void Misc_string_compare_w_string(void); +void Misc_string_view_compare_w_char_ptr(void); +void Misc_string_view_compare_w_string(void); +void Misc_string_compare_nullptr(void); +void Misc_nullptr_string_compare(void); +void Misc_nullptr_string_compare_nullptr(void); + +bake_test_case Entity_testcases[] = { + { + "new", + Entity_new + }, + { + "new_named", + Entity_new_named + }, + { + "new_named_from_scope", + Entity_new_named_from_scope + }, + { + "new_nested_named_from_scope", + Entity_new_nested_named_from_scope + }, + { + "new_nested_named_from_nested_scope", + Entity_new_nested_named_from_nested_scope + }, + { + "new_add", + Entity_new_add + }, + { + "new_add_2", + Entity_new_add_2 + }, + { + "new_set", + Entity_new_set + }, + { + "new_set_2", + Entity_new_set_2 + }, + { + "add", + Entity_add + }, + { + "add_2", + Entity_add_2 + }, + { + "add_entity", + Entity_add_entity + }, + { + "add_childof", + Entity_add_childof + }, + { + "add_instanceof", + Entity_add_instanceof + }, + { + "remove", + Entity_remove + }, + { + "remove_2", + Entity_remove_2 + }, + { + "remove_entity", + Entity_remove_entity + }, + { + "remove_childof", + Entity_remove_childof + }, + { + "remove_instanceof", + Entity_remove_instanceof + }, + { + "set", + Entity_set + }, + { + "set_2", + Entity_set_2 + }, + { + "emplace", + Entity_emplace + }, + { + "emplace_2", + Entity_emplace_2 + }, + { + "emplace_after_add", + Entity_emplace_after_add + }, + { + "emplace_after_add_pair", + Entity_emplace_after_add_pair + }, + { + "emplace_w_self_ctor", + Entity_emplace_w_self_ctor + }, + { + "replace", + Entity_replace + }, + { + "get_generic", + Entity_get_generic + }, + { + "get_mut_generic", + Entity_get_mut_generic + }, + { + "get_generic_w_id", + Entity_get_generic_w_id + }, + { + "get_mut_generic_w_id", + Entity_get_mut_generic_w_id + }, + { + "add_role", + Entity_add_role + }, + { + "remove_role", + Entity_remove_role + }, + { + "has_role", + Entity_has_role + }, + { + "pair_role", + Entity_pair_role + }, + { + "equals", + Entity_equals + }, + { + "compare_0", + Entity_compare_0 + }, + { + "compare_id_t", + Entity_compare_id_t + }, + { + "compare_id", + Entity_compare_id + }, + { + "compare_literal", + Entity_compare_literal + }, + { + "greater_than", + Entity_greater_than + }, + { + "less_than", + Entity_less_than + }, + { + "not_0_or_1", + Entity_not_0_or_1 + }, + { + "not_true_or_false", + Entity_not_true_or_false + }, + { + "has_childof", + Entity_has_childof + }, + { + "has_instanceof", + Entity_has_instanceof + }, + { + "has_instanceof_indirect", + Entity_has_instanceof_indirect + }, + { + "null_string", + Entity_null_string + }, + { + "set_name", + Entity_set_name + }, + { + "change_name", + Entity_change_name + }, + { + "delete", + Entity_delete + }, + { + "clear", + Entity_clear + }, + { + "foce_owned", + Entity_foce_owned + }, + { + "force_owned_2", + Entity_force_owned_2 + }, + { + "force_owned_nested", + Entity_force_owned_nested + }, + { + "force_owned_type", + Entity_force_owned_type + }, + { + "force_owned_type_w_pair", + Entity_force_owned_type_w_pair + }, + { + "tag_has_size_zero", + Entity_tag_has_size_zero + }, + { + "get_null_name", + Entity_get_null_name + }, + { + "get_parent", + Entity_get_parent + }, + { + "is_component_enabled", + Entity_is_component_enabled + }, + { + "is_enabled_component_enabled", + Entity_is_enabled_component_enabled + }, + { + "is_disabled_component_enabled", + Entity_is_disabled_component_enabled + }, + { + "get_type", + Entity_get_type + }, + { + "get_nonempty_type", + Entity_get_nonempty_type + }, + { + "set_no_copy", + Entity_set_no_copy + }, + { + "set_copy", + Entity_set_copy + }, + { + "set_deduced", + Entity_set_deduced + }, + { + "add_owned", + Entity_add_owned + }, + { + "set_owned", + Entity_set_owned + }, + { + "implicit_name_to_char", + Entity_implicit_name_to_char + }, + { + "implicit_path_to_char", + Entity_implicit_path_to_char + }, + { + "implicit_type_str_to_char", + Entity_implicit_type_str_to_char + }, + { + "entity_to_entity_view", + Entity_entity_to_entity_view + }, + { + "entity_view_to_entity_world", + Entity_entity_view_to_entity_world + }, + { + "entity_view_to_entity_stage", + Entity_entity_view_to_entity_stage + }, + { + "create_entity_view_from_stage", + Entity_create_entity_view_from_stage + }, + { + "set_template", + Entity_set_template + }, + { + "get_1_component_w_callback", + Entity_get_1_component_w_callback + }, + { + "get_2_components_w_callback", + Entity_get_2_components_w_callback + }, + { + "set_1_component_w_callback", + Entity_set_1_component_w_callback + }, + { + "set_2_components_w_callback", + Entity_set_2_components_w_callback + }, + { + "set_3_components_w_callback", + Entity_set_3_components_w_callback + }, + { + "defer_set_1_component", + Entity_defer_set_1_component + }, + { + "defer_set_2_components", + Entity_defer_set_2_components + }, + { + "defer_set_3_components", + Entity_defer_set_3_components + }, + { + "set_2_w_on_set", + Entity_set_2_w_on_set + }, + { + "defer_set_2_w_on_set", + Entity_defer_set_2_w_on_set + }, + { + "set_2_after_fluent", + Entity_set_2_after_fluent + }, + { + "set_2_before_fluent", + Entity_set_2_before_fluent + }, + { + "set_2_after_set_1", + Entity_set_2_after_set_1 + }, + { + "set_2_after_set_2", + Entity_set_2_after_set_2 + }, + { + "with_self", + Entity_with_self + }, + { + "with_relation_type_self", + Entity_with_relation_type_self + }, + { + "with_relation_self", + Entity_with_relation_self + }, + { + "with_self_w_name", + Entity_with_self_w_name + }, + { + "with_self_nested", + Entity_with_self_nested + }, + { + "with_after_builder_method", + Entity_with_after_builder_method + }, + { + "with_before_builder_method", + Entity_with_before_builder_method + }, + { + "with_scope", + Entity_with_scope + }, + { + "with_scope_nested", + Entity_with_scope_nested + }, + { + "with_scope_nested_same_name_as_parent", + Entity_with_scope_nested_same_name_as_parent + }, + { + "scope_after_builder_method", + Entity_scope_after_builder_method + }, + { + "scope_before_builder_method", + Entity_scope_before_builder_method + }, + { + "no_recursive_lookup", + Entity_no_recursive_lookup + }, + { + "defer_new_w_name", + Entity_defer_new_w_name + }, + { + "defer_new_w_nested_name", + Entity_defer_new_w_nested_name + }, + { + "defer_new_w_scope_name", + Entity_defer_new_w_scope_name + }, + { + "defer_new_w_scope_nested_name", + Entity_defer_new_w_scope_nested_name + }, + { + "defer_new_w_deferred_scope_nested_name", + Entity_defer_new_w_deferred_scope_nested_name + }, + { + "defer_new_w_scope", + Entity_defer_new_w_scope + }, + { + "defer_new_w_with", + Entity_defer_new_w_with + }, + { + "defer_new_w_name_scope_with", + Entity_defer_new_w_name_scope_with + }, + { + "defer_w_with_implicit_component", + Entity_defer_w_with_implicit_component + }, + { + "entity_id_str", + Entity_entity_id_str + }, + { + "pair_id_str", + Entity_pair_id_str + }, + { + "role_id_str", + Entity_role_id_str + }, + { + "id_str_from_entity_view", + Entity_id_str_from_entity_view + }, + { + "id_str_from_entity", + Entity_id_str_from_entity + }, + { + "null_entity", + Entity_null_entity + }, + { + "null_entity_w_world", + Entity_null_entity_w_world + }, + { + "null_entity_w_0", + Entity_null_entity_w_0 + }, + { + "null_entity_w_world_w_0", + Entity_null_entity_w_world_w_0 + }, + { + "entity_view_null_entity", + Entity_entity_view_null_entity + }, + { + "entity_view_null_entity_w_world", + Entity_entity_view_null_entity_w_world + }, + { + "entity_view_null_entity_w_0", + Entity_entity_view_null_entity_w_0 + }, + { + "entity_view_null_entity_w_world_w_0", + Entity_entity_view_null_entity_w_world_w_0 + }, + { + "is_wildcard", + Entity_is_wildcard + }, + { + "has_id_t", + Entity_has_id_t + }, + { + "has_pair_id_t", + Entity_has_pair_id_t + }, + { + "has_pair_id_t_w_type", + Entity_has_pair_id_t_w_type + }, + { + "has_id", + Entity_has_id + }, + { + "has_pair_id", + Entity_has_pair_id + }, + { + "has_pair_id_w_type", + Entity_has_pair_id_w_type + }, + { + "has_wildcard_id", + Entity_has_wildcard_id + }, + { + "has_wildcard_pair_id", + Entity_has_wildcard_pair_id + }, + { + "owns_id_t", + Entity_owns_id_t + }, + { + "owns_pair_id_t", + Entity_owns_pair_id_t + }, + { + "owns_pair_id_t_w_type", + Entity_owns_pair_id_t_w_type + }, + { + "owns_id", + Entity_owns_id + }, + { + "owns_pair_id", + Entity_owns_pair_id + }, + { + "owns_pair_id_w_type", + Entity_owns_pair_id_w_type + }, + { + "owns_wildcard_id", + Entity_owns_wildcard_id + }, + { + "owns_wildcard_pair", + Entity_owns_wildcard_pair + }, + { + "id_from_world", + Entity_id_from_world + }, + { + "id_pair_from_world", + Entity_id_pair_from_world + }, + { + "id_default_from_world", + Entity_id_default_from_world + }, + { + "is_a", + Entity_is_a + }, + { + "is_a_w_type", + Entity_is_a_w_type + }, + { + "child_of", + Entity_child_of + }, + { + "child_of_w_type", + Entity_child_of_w_type + }, + { + "id_get_entity", + Entity_id_get_entity + }, + { + "id_get_invalid_entity", + Entity_id_get_invalid_entity + } +}; + +bake_test_case Pairs_testcases[] = { + { + "add_component_pair", + Pairs_add_component_pair + }, + { + "add_tag_pair", + Pairs_add_tag_pair + }, + { + "add_tag_pair_to_tag", + Pairs_add_tag_pair_to_tag + }, + { + "remove_component_pair", + Pairs_remove_component_pair + }, + { + "remove_tag_pair", + Pairs_remove_tag_pair + }, + { + "remove_tag_pair_to_tag", + Pairs_remove_tag_pair_to_tag + }, + { + "set_component_pair", + Pairs_set_component_pair + }, + { + "set_tag_pair", + Pairs_set_tag_pair + }, + { + "system_1_pair_instance", + Pairs_system_1_pair_instance + }, + { + "system_2_pair_instances", + Pairs_system_2_pair_instances + }, + { + "override_pair", + Pairs_override_pair + }, + { + "override_tag_pair", + Pairs_override_tag_pair + }, + { + "get_mut_pair", + Pairs_get_mut_pair + }, + { + "get_mut_pair_existing", + Pairs_get_mut_pair_existing + }, + { + "get_mut_pair_tag", + Pairs_get_mut_pair_tag + }, + { + "get_mut_pair_tag_existing", + Pairs_get_mut_pair_tag_existing + }, + { + "type_w_pair", + Pairs_type_w_pair + }, + { + "type_w_pair_tag", + Pairs_type_w_pair_tag + }, + { + "type_w_pair_tags", + Pairs_type_w_pair_tags + }, + { + "type_w_tag_pair", + Pairs_type_w_tag_pair + }, + { + "override_pair_w_type", + Pairs_override_pair_w_type + }, + { + "override_pair_w_type_tag", + Pairs_override_pair_w_type_tag + }, + { + "override_tag_pair_w_type", + Pairs_override_tag_pair_w_type + }, + { + "get_relation_from_id", + Pairs_get_relation_from_id + }, + { + "get_w_object_from_id", + Pairs_get_w_object_from_id + }, + { + "get_recycled_relation_from_id", + Pairs_get_recycled_relation_from_id + }, + { + "get_recycled_object_from_id", + Pairs_get_recycled_object_from_id + }, + { + "each", + Pairs_each + }, + { + "each_pair", + Pairs_each_pair + }, + { + "each_pair_by_type", + Pairs_each_pair_by_type + }, + { + "each_pair_w_childof", + Pairs_each_pair_w_childof + }, + { + "each_pair_w_recycled_rel", + Pairs_each_pair_w_recycled_rel + }, + { + "each_pair_w_recycled_obj", + Pairs_each_pair_w_recycled_obj + }, + { + "match_pair", + Pairs_match_pair + }, + { + "match_pair_obj_wildcard", + Pairs_match_pair_obj_wildcard + }, + { + "match_pair_rel_wildcard", + Pairs_match_pair_rel_wildcard + }, + { + "match_pair_both_wildcard", + Pairs_match_pair_both_wildcard + }, + { + "has_tag_w_object", + Pairs_has_tag_w_object + }, + { + "has_w_object_tag", + Pairs_has_w_object_tag + }, + { + "add_pair_type", + Pairs_add_pair_type + }, + { + "remove_pair_type", + Pairs_remove_pair_type + }, + { + "set_pair_type", + Pairs_set_pair_type + }, + { + "has_pair_type", + Pairs_has_pair_type + }, + { + "get_1_pair_arg", + Pairs_get_1_pair_arg + }, + { + "get_2_pair_arg", + Pairs_get_2_pair_arg + }, + { + "set_1_pair_arg", + Pairs_set_1_pair_arg + }, + { + "set_2_pair_arg", + Pairs_set_2_pair_arg + }, + { + "get_inline_pair_type", + Pairs_get_inline_pair_type + }, + { + "set_inline_pair_type", + Pairs_set_inline_pair_type + }, + { + "get_pair_type_object", + Pairs_get_pair_type_object + }, + { + "set_pair_type_object", + Pairs_set_pair_type_object + }, + { + "set_get_w_object_variants", + Pairs_set_get_w_object_variants + } +}; + +bake_test_case Switch_testcases[] = { + { + "add_case", + Switch_add_case + }, + { + "get_case", + Switch_get_case + }, + { + "system_w_case", + Switch_system_w_case + }, + { + "system_w_switch", + Switch_system_w_switch + }, + { + "add_case_w_type", + Switch_add_case_w_type + }, + { + "add_switch_w_type", + Switch_add_switch_w_type + }, + { + "add_switch_w_type_component_first", + Switch_add_switch_w_type_component_first + }, + { + "add_remove_switch_w_type", + Switch_add_remove_switch_w_type + } +}; + +bake_test_case Paths_testcases[] = { + { + "name", + Paths_name + }, + { + "path_depth_1", + Paths_path_depth_1 + }, + { + "path_depth_2", + Paths_path_depth_2 + }, + { + "entity_lookup_name", + Paths_entity_lookup_name + }, + { + "entity_lookup_depth_1", + Paths_entity_lookup_depth_1 + }, + { + "entity_lookup_depth_2", + Paths_entity_lookup_depth_2 + }, + { + "alias_component", + Paths_alias_component + }, + { + "alias_scoped_component", + Paths_alias_scoped_component + }, + { + "alias_scoped_component_w_name", + Paths_alias_scoped_component_w_name + }, + { + "alias_entity", + Paths_alias_entity + }, + { + "alias_entity_by_name", + Paths_alias_entity_by_name + }, + { + "alias_entity_by_scoped_name", + Paths_alias_entity_by_scoped_name + } +}; + +bake_test_case Type_testcases[] = { + { + "add_2", + Type_add_2 + }, + { + "add_instanceof", + Type_add_instanceof + }, + { + "add_childof", + Type_add_childof + }, + { + "1_component", + Type_1_component + }, + { + "2_component", + Type_2_component + }, + { + "1_component_signature", + Type_1_component_signature + }, + { + "2_component_signature", + Type_2_component_signature + }, + { + "type_no_name", + Type_type_no_name + }, + { + "null_args", + Type_null_args + }, + { + "has_type", + Type_has_type + }, + { + "has_entity", + Type_has_entity + }, + { + "has_pair_type", + Type_has_pair_type + }, + { + "has_pair_entity", + Type_has_pair_entity + }, + { + "get", + Type_get + }, + { + "get_out_of_range", + Type_get_out_of_range + } +}; + +bake_test_case System_testcases[] = { + { + "action", + System_action + }, + { + "action_const", + System_action_const + }, + { + "action_shared", + System_action_shared + }, + { + "action_optional", + System_action_optional + }, + { + "each", + System_each + }, + { + "each_const", + System_each_const + }, + { + "each_shared", + System_each_shared + }, + { + "each_optional", + System_each_optional + }, + { + "signature", + System_signature + }, + { + "signature_const", + System_signature_const + }, + { + "signature_shared", + System_signature_shared + }, + { + "signature_optional", + System_signature_optional + }, + { + "copy_name_on_create", + System_copy_name_on_create + }, + { + "nested_system", + System_nested_system + }, + { + "empty_signature", + System_empty_signature + }, + { + "action_tag", + System_action_tag + }, + { + "iter_tag", + System_iter_tag + }, + { + "each_tag", + System_each_tag + }, + { + "system_from_id", + System_system_from_id + }, + { + "set_interval", + System_set_interval + }, + { + "order_by_type", + System_order_by_type + }, + { + "order_by_id", + System_order_by_id + }, + { + "order_by_type_after_create", + System_order_by_type_after_create + }, + { + "order_by_id_after_create", + System_order_by_id_after_create + }, + { + "get_query", + System_get_query + }, + { + "add_from_each", + System_add_from_each + }, + { + "delete_from_each", + System_delete_from_each + }, + { + "add_from_each_world_handle", + System_add_from_each_world_handle + }, + { + "new_from_each", + System_new_from_each + }, + { + "add_from_iter", + System_add_from_iter + }, + { + "delete_from_iter", + System_delete_from_iter + }, + { + "add_from_iter_world_handle", + System_add_from_iter_world_handle + }, + { + "new_from_iter", + System_new_from_iter + }, + { + "each_w_mut_children_it", + System_each_w_mut_children_it + }, + { + "readonly_children_iter", + System_readonly_children_iter + }, + { + "rate_filter", + System_rate_filter + }, + { + "update_rate_filter", + System_update_rate_filter + }, + { + "default_ctor", + System_default_ctor + }, + { + "test_auto_defer_each", + System_test_auto_defer_each + }, + { + "test_auto_defer_iter", + System_test_auto_defer_iter + }, + { + "custom_pipeline", + System_custom_pipeline + }, + { + "system_w_self", + System_system_w_self + } +}; + +bake_test_case Trigger_testcases[] = { + { + "on_add", + Trigger_on_add + }, + { + "on_remove", + Trigger_on_remove + }, + { + "on_add_tag_action", + Trigger_on_add_tag_action + }, + { + "on_add_tag_iter", + Trigger_on_add_tag_iter + }, + { + "on_add_tag_each", + Trigger_on_add_tag_each + }, + { + "trigger_w_self", + Trigger_trigger_w_self + } +}; + +bake_test_case Query_testcases[] = { + { + "action", + Query_action + }, + { + "action_const", + Query_action_const + }, + { + "action_shared", + Query_action_shared + }, + { + "action_optional", + Query_action_optional + }, + { + "each", + Query_each + }, + { + "each_const", + Query_each_const + }, + { + "each_shared", + Query_each_shared + }, + { + "each_optional", + Query_each_optional + }, + { + "signature", + Query_signature + }, + { + "signature_const", + Query_signature_const + }, + { + "signature_shared", + Query_signature_shared + }, + { + "signature_optional", + Query_signature_optional + }, + { + "subquery", + Query_subquery + }, + { + "subquery_w_expr", + Query_subquery_w_expr + }, + { + "query_single_pair", + Query_query_single_pair + }, + { + "tag_w_each", + Query_tag_w_each + }, + { + "shared_tag_w_each", + Query_shared_tag_w_each + }, + { + "sort_by", + Query_sort_by + }, + { + "changed", + Query_changed + }, + { + "orphaned", + Query_orphaned + }, + { + "default_ctor", + Query_default_ctor + }, + { + "expr_w_template", + Query_expr_w_template + }, + { + "query_type_w_template", + Query_query_type_w_template + }, + { + "compare_term_id", + Query_compare_term_id + }, + { + "test_no_defer_each", + Query_test_no_defer_each + }, + { + "test_no_defer_iter", + Query_test_no_defer_iter + }, + { + "inspect_terms", + Query_inspect_terms + }, + { + "inspect_terms_w_each", + Query_inspect_terms_w_each + }, + { + "comp_to_str", + Query_comp_to_str + }, + { + "pair_to_str", + Query_pair_to_str + }, + { + "oper_not_to_str", + Query_oper_not_to_str + }, + { + "oper_optional_to_str", + Query_oper_optional_to_str + }, + { + "oper_or_to_str", + Query_oper_or_to_str + }, + { + "each_pair_type", + Query_each_pair_type + }, + { + "iter_pair_type", + Query_iter_pair_type + }, + { + "term_pair_type", + Query_term_pair_type + }, + { + "each_no_entity_1_comp", + Query_each_no_entity_1_comp + }, + { + "each_no_entity_2_comps", + Query_each_no_entity_2_comps + }, + { + "iter_no_comps_1_comp", + Query_iter_no_comps_1_comp + }, + { + "iter_no_comps_2_comps", + Query_iter_no_comps_2_comps + }, + { + "iter_no_comps_no_comps", + Query_iter_no_comps_no_comps + }, + { + "each_pair_object", + Query_each_pair_object + }, + { + "iter_pair_object", + Query_iter_pair_object + }, + { + "iter_query_in_system", + Query_iter_query_in_system + }, + { + "iter_type", + Query_iter_type + } +}; + +bake_test_case QueryBuilder_testcases[] = { + { + "builder_assign_same_type", + QueryBuilder_builder_assign_same_type + }, + { + "builder_assign_from_empty", + QueryBuilder_builder_assign_from_empty + }, + { + "builder_assign_to_empty", + QueryBuilder_builder_assign_to_empty + }, + { + "builder_build", + QueryBuilder_builder_build + }, + { + "builder_build_to_auto", + QueryBuilder_builder_build_to_auto + }, + { + "builder_build_n_statements", + QueryBuilder_builder_build_n_statements + }, + { + "1_type", + QueryBuilder_1_type + }, + { + "add_1_type", + QueryBuilder_add_1_type + }, + { + "add_2_types", + QueryBuilder_add_2_types + }, + { + "add_1_type_w_1_type", + QueryBuilder_add_1_type_w_1_type + }, + { + "add_2_types_w_1_type", + QueryBuilder_add_2_types_w_1_type + }, + { + "add_pair", + QueryBuilder_add_pair + }, + { + "add_not", + QueryBuilder_add_not + }, + { + "add_or", + QueryBuilder_add_or + }, + { + "add_optional", + QueryBuilder_add_optional + }, + { + "ptr_type", + QueryBuilder_ptr_type + }, + { + "const_type", + QueryBuilder_const_type + }, + { + "string_term", + QueryBuilder_string_term + }, + { + "singleton_term", + QueryBuilder_singleton_term + }, + { + "isa_superset_term", + QueryBuilder_isa_superset_term + }, + { + "isa_self_superset_term", + QueryBuilder_isa_self_superset_term + }, + { + "childof_superset_term", + QueryBuilder_childof_superset_term + }, + { + "childof_self_superset_term", + QueryBuilder_childof_self_superset_term + }, + { + "isa_superset_term_w_each", + QueryBuilder_isa_superset_term_w_each + }, + { + "isa_self_superset_term_w_each", + QueryBuilder_isa_self_superset_term_w_each + }, + { + "childof_superset_term_w_each", + QueryBuilder_childof_superset_term_w_each + }, + { + "childof_self_superset_term_w_each", + QueryBuilder_childof_self_superset_term_w_each + }, + { + "isa_superset_shortcut", + QueryBuilder_isa_superset_shortcut + }, + { + "isa_superset_shortcut_w_self", + QueryBuilder_isa_superset_shortcut_w_self + }, + { + "childof_superset_shortcut", + QueryBuilder_childof_superset_shortcut + }, + { + "childof_superset_shortcut_w_self", + QueryBuilder_childof_superset_shortcut_w_self + }, + { + "isa_superset_max_depth_1", + QueryBuilder_isa_superset_max_depth_1 + }, + { + "isa_superset_max_depth_2", + QueryBuilder_isa_superset_max_depth_2 + }, + { + "isa_superset_min_depth_2", + QueryBuilder_isa_superset_min_depth_2 + }, + { + "isa_superset_min_depth_2_max_depth_3", + QueryBuilder_isa_superset_min_depth_2_max_depth_3 + }, + { + "role", + QueryBuilder_role + }, + { + "relation", + QueryBuilder_relation + }, + { + "relation_w_object_wildcard", + QueryBuilder_relation_w_object_wildcard + }, + { + "relation_w_predicate_wildcard", + QueryBuilder_relation_w_predicate_wildcard + }, + { + "add_pair_w_rel_type", + QueryBuilder_add_pair_w_rel_type + }, + { + "template_term", + QueryBuilder_template_term + }, + { + "explicit_subject_w_id", + QueryBuilder_explicit_subject_w_id + }, + { + "explicit_subject_w_type", + QueryBuilder_explicit_subject_w_type + }, + { + "explicit_object_w_id", + QueryBuilder_explicit_object_w_id + }, + { + "explicit_object_w_type", + QueryBuilder_explicit_object_w_type + }, + { + "explicit_term", + QueryBuilder_explicit_term + }, + { + "explicit_term_w_type", + QueryBuilder_explicit_term_w_type + }, + { + "explicit_term_w_pair_type", + QueryBuilder_explicit_term_w_pair_type + }, + { + "explicit_term_w_id", + QueryBuilder_explicit_term_w_id + }, + { + "explicit_term_w_pair_id", + QueryBuilder_explicit_term_w_pair_id + }, + { + "1_term_to_empty", + QueryBuilder_1_term_to_empty + }, + { + "2_subsequent_args", + QueryBuilder_2_subsequent_args + }, + { + "optional_tag_is_set", + QueryBuilder_optional_tag_is_set + }, + { + "10_terms", + QueryBuilder_10_terms + }, + { + "20_terms", + QueryBuilder_20_terms + } +}; + +bake_test_case FilterBuilder_testcases[] = { + { + "builder_assign_same_type", + FilterBuilder_builder_assign_same_type + }, + { + "builder_assign_from_empty", + FilterBuilder_builder_assign_from_empty + }, + { + "builder_assign_to_empty", + FilterBuilder_builder_assign_to_empty + }, + { + "builder_build", + FilterBuilder_builder_build + }, + { + "builder_build_to_auto", + FilterBuilder_builder_build_to_auto + }, + { + "builder_build_n_statements", + FilterBuilder_builder_build_n_statements + }, + { + "1_type", + FilterBuilder_1_type + }, + { + "add_1_type", + FilterBuilder_add_1_type + }, + { + "add_2_types", + FilterBuilder_add_2_types + }, + { + "add_1_type_w_1_type", + FilterBuilder_add_1_type_w_1_type + }, + { + "add_2_types_w_1_type", + FilterBuilder_add_2_types_w_1_type + }, + { + "add_pair", + FilterBuilder_add_pair + }, + { + "add_not", + FilterBuilder_add_not + }, + { + "add_or", + FilterBuilder_add_or + }, + { + "add_optional", + FilterBuilder_add_optional + }, + { + "ptr_type", + FilterBuilder_ptr_type + }, + { + "const_type", + FilterBuilder_const_type + }, + { + "string_term", + FilterBuilder_string_term + }, + { + "singleton_term", + FilterBuilder_singleton_term + }, + { + "isa_superset_term", + FilterBuilder_isa_superset_term + }, + { + "isa_self_superset_term", + FilterBuilder_isa_self_superset_term + }, + { + "childof_superset_term", + FilterBuilder_childof_superset_term + }, + { + "childof_self_superset_term", + FilterBuilder_childof_self_superset_term + }, + { + "isa_superset_term_w_each", + FilterBuilder_isa_superset_term_w_each + }, + { + "isa_self_superset_term_w_each", + FilterBuilder_isa_self_superset_term_w_each + }, + { + "childof_superset_term_w_each", + FilterBuilder_childof_superset_term_w_each + }, + { + "childof_self_superset_term_w_each", + FilterBuilder_childof_self_superset_term_w_each + }, + { + "isa_superset_shortcut", + FilterBuilder_isa_superset_shortcut + }, + { + "isa_superset_shortcut_w_self", + FilterBuilder_isa_superset_shortcut_w_self + }, + { + "childof_superset_shortcut", + FilterBuilder_childof_superset_shortcut + }, + { + "childof_superset_shortcut_w_self", + FilterBuilder_childof_superset_shortcut_w_self + }, + { + "isa_superset_max_depth_1", + FilterBuilder_isa_superset_max_depth_1 + }, + { + "isa_superset_max_depth_2", + FilterBuilder_isa_superset_max_depth_2 + }, + { + "isa_superset_min_depth_2", + FilterBuilder_isa_superset_min_depth_2 + }, + { + "isa_superset_min_depth_2_max_depth_3", + FilterBuilder_isa_superset_min_depth_2_max_depth_3 + }, + { + "relation", + FilterBuilder_relation + }, + { + "relation_w_object_wildcard", + FilterBuilder_relation_w_object_wildcard + }, + { + "relation_w_predicate_wildcard", + FilterBuilder_relation_w_predicate_wildcard + }, + { + "add_pair_w_rel_type", + FilterBuilder_add_pair_w_rel_type + }, + { + "template_term", + FilterBuilder_template_term + }, + { + "explicit_subject_w_id", + FilterBuilder_explicit_subject_w_id + }, + { + "explicit_subject_w_type", + FilterBuilder_explicit_subject_w_type + }, + { + "explicit_object_w_id", + FilterBuilder_explicit_object_w_id + }, + { + "explicit_object_w_type", + FilterBuilder_explicit_object_w_type + }, + { + "explicit_term", + FilterBuilder_explicit_term + }, + { + "explicit_term_w_type", + FilterBuilder_explicit_term_w_type + }, + { + "explicit_term_w_pair_type", + FilterBuilder_explicit_term_w_pair_type + }, + { + "explicit_term_w_id", + FilterBuilder_explicit_term_w_id + }, + { + "explicit_term_w_pair_id", + FilterBuilder_explicit_term_w_pair_id + }, + { + "1_term_to_empty", + FilterBuilder_1_term_to_empty + }, + { + "2_subsequent_args", + FilterBuilder_2_subsequent_args + }, + { + "filter_as_arg", + FilterBuilder_filter_as_arg + }, + { + "filter_as_move_arg", + FilterBuilder_filter_as_move_arg + }, + { + "filter_as_return", + FilterBuilder_filter_as_return + }, + { + "filter_copy", + FilterBuilder_filter_copy + }, + { + "world_each_filter_1_component", + FilterBuilder_world_each_filter_1_component + }, + { + "world_each_filter_2_components", + FilterBuilder_world_each_filter_2_components + }, + { + "world_each_filter_1_component_no_entity", + FilterBuilder_world_each_filter_1_component_no_entity + }, + { + "world_each_filter_2_components_no_entity", + FilterBuilder_world_each_filter_2_components_no_entity + }, + { + "10_terms", + FilterBuilder_10_terms + }, + { + "20_terms", + FilterBuilder_20_terms + }, + { + "term_after_arg", + FilterBuilder_term_after_arg + } +}; + +bake_test_case SystemBuilder_testcases[] = { + { + "builder_assign_same_type", + SystemBuilder_builder_assign_same_type + }, + { + "builder_assign_from_empty", + SystemBuilder_builder_assign_from_empty + }, + { + "builder_build_to_auto", + SystemBuilder_builder_build_to_auto + }, + { + "builder_build_n_statements", + SystemBuilder_builder_build_n_statements + }, + { + "1_type", + SystemBuilder_1_type + }, + { + "add_1_type", + SystemBuilder_add_1_type + }, + { + "add_2_types", + SystemBuilder_add_2_types + }, + { + "add_1_type_w_1_type", + SystemBuilder_add_1_type_w_1_type + }, + { + "add_2_types_w_1_type", + SystemBuilder_add_2_types_w_1_type + }, + { + "add_pair", + SystemBuilder_add_pair + }, + { + "add_not", + SystemBuilder_add_not + }, + { + "add_or", + SystemBuilder_add_or + }, + { + "add_optional", + SystemBuilder_add_optional + }, + { + "ptr_type", + SystemBuilder_ptr_type + }, + { + "const_type", + SystemBuilder_const_type + }, + { + "string_term", + SystemBuilder_string_term + }, + { + "singleton_term", + SystemBuilder_singleton_term + }, + { + "10_terms", + SystemBuilder_10_terms + }, + { + "20_terms", + SystemBuilder_20_terms + } +}; + +bake_test_case Observer_testcases[] = { + { + "2_terms_on_add", + Observer_2_terms_on_add + }, + { + "2_terms_on_remove", + Observer_2_terms_on_remove + }, + { + "2_terms_on_set", + Observer_2_terms_on_set + }, + { + "2_terms_un_set", + Observer_2_terms_un_set + }, + { + "observer_w_self", + Observer_observer_w_self + }, + { + "10_terms", + Observer_10_terms + }, + { + "20_terms", + Observer_20_terms + } +}; + +bake_test_case Filter_testcases[] = { + { + "term_each_component", + Filter_term_each_component + }, + { + "term_each_tag", + Filter_term_each_tag + }, + { + "term_each_id", + Filter_term_each_id + }, + { + "term_each_pair_type", + Filter_term_each_pair_type + }, + { + "term_each_pair_id", + Filter_term_each_pair_id + }, + { + "term_each_pair_relation_wildcard", + Filter_term_each_pair_relation_wildcard + }, + { + "term_each_pair_object_wildcard", + Filter_term_each_pair_object_wildcard + } +}; + +bake_test_case ComponentLifecycle_testcases[] = { + { + "ctor_on_add", + ComponentLifecycle_ctor_on_add + }, + { + "dtor_on_remove", + ComponentLifecycle_dtor_on_remove + }, + { + "move_on_add", + ComponentLifecycle_move_on_add + }, + { + "move_on_remove", + ComponentLifecycle_move_on_remove + }, + { + "copy_on_set", + ComponentLifecycle_copy_on_set + }, + { + "copy_on_override", + ComponentLifecycle_copy_on_override + }, + { + "non_pod_add", + ComponentLifecycle_non_pod_add + }, + { + "non_pod_remove", + ComponentLifecycle_non_pod_remove + }, + { + "non_pod_set", + ComponentLifecycle_non_pod_set + }, + { + "non_pod_override", + ComponentLifecycle_non_pod_override + }, + { + "get_mut_new", + ComponentLifecycle_get_mut_new + }, + { + "get_mut_existing", + ComponentLifecycle_get_mut_existing + }, + { + "pod_component", + ComponentLifecycle_pod_component + }, + { + "relocatable_component", + ComponentLifecycle_relocatable_component + }, + { + "implicit_component", + ComponentLifecycle_implicit_component + }, + { + "implicit_after_query", + ComponentLifecycle_implicit_after_query + }, + { + "deleted_copy", + ComponentLifecycle_deleted_copy + }, + { + "no_default_ctor_emplace", + ComponentLifecycle_no_default_ctor_emplace + }, + { + "default_init", + ComponentLifecycle_default_init + }, + { + "no_default_ctor_add", + ComponentLifecycle_no_default_ctor_add + }, + { + "no_default_ctor_add_relation", + ComponentLifecycle_no_default_ctor_add_relation + }, + { + "no_default_ctor_add_w_object", + ComponentLifecycle_no_default_ctor_add_w_object + }, + { + "no_default_ctor_set", + ComponentLifecycle_no_default_ctor_set + }, + { + "no_copy_ctor", + ComponentLifecycle_no_copy_ctor + }, + { + "no_move_ctor", + ComponentLifecycle_no_move_ctor + }, + { + "no_copy_assign", + ComponentLifecycle_no_copy_assign + }, + { + "no_move_assign", + ComponentLifecycle_no_move_assign + }, + { + "no_copy", + ComponentLifecycle_no_copy + }, + { + "no_move", + ComponentLifecycle_no_move + }, + { + "no_dtor", + ComponentLifecycle_no_dtor + }, + { + "flecs_ctor", + ComponentLifecycle_flecs_ctor + }, + { + "flecs_ctor_w_default_ctor", + ComponentLifecycle_flecs_ctor_w_default_ctor + }, + { + "default_ctor_w_value_ctor", + ComponentLifecycle_default_ctor_w_value_ctor + }, + { + "flecs_ctor_w_value_ctor", + ComponentLifecycle_flecs_ctor_w_value_ctor + }, + { + "no_default_ctor_move_ctor_on_set", + ComponentLifecycle_no_default_ctor_move_ctor_on_set + }, + { + "emplace_w_ctor", + ComponentLifecycle_emplace_w_ctor + }, + { + "emplace_no_default_ctor", + ComponentLifecycle_emplace_no_default_ctor + }, + { + "emplace_existing", + ComponentLifecycle_emplace_existing + }, + { + "emplace_singleton", + ComponentLifecycle_emplace_singleton + }, + { + "dtor_w_non_trivial_implicit_move", + ComponentLifecycle_dtor_w_non_trivial_implicit_move + }, + { + "dtor_w_non_trivial_explicit_move", + ComponentLifecycle_dtor_w_non_trivial_explicit_move + } +}; + +bake_test_case Refs_testcases[] = { + { + "get_ref", + Refs_get_ref + }, + { + "ref_after_add", + Refs_ref_after_add + }, + { + "ref_after_remove", + Refs_ref_after_remove + }, + { + "ref_after_set", + Refs_ref_after_set + }, + { + "ref_before_set", + Refs_ref_before_set + } +}; + +bake_test_case Module_testcases[] = { + { + "import", + Module_import + }, + { + "lookup_from_scope", + Module_lookup_from_scope + }, + { + "nested_module", + Module_nested_module + }, + { + "nested_type_module", + Module_nested_type_module + }, + { + "module_type_w_explicit_name", + Module_module_type_w_explicit_name + }, + { + "component_redefinition_outside_module", + Module_component_redefinition_outside_module + }, + { + "module_tag_on_namespace", + Module_module_tag_on_namespace + }, + { + "dtor_on_fini", + Module_dtor_on_fini + } +}; + +bake_test_case ImplicitComponents_testcases[] = { + { + "add", + ImplicitComponents_add + }, + { + "remove", + ImplicitComponents_remove + }, + { + "has", + ImplicitComponents_has + }, + { + "set", + ImplicitComponents_set + }, + { + "get", + ImplicitComponents_get + }, + { + "add_pair", + ImplicitComponents_add_pair + }, + { + "remove_pair", + ImplicitComponents_remove_pair + }, + { + "module", + ImplicitComponents_module + }, + { + "system", + ImplicitComponents_system + }, + { + "system_optional", + ImplicitComponents_system_optional + }, + { + "system_const", + ImplicitComponents_system_const + }, + { + "query", + ImplicitComponents_query + }, + { + "implicit_name", + ImplicitComponents_implicit_name + }, + { + "reinit", + ImplicitComponents_reinit + }, + { + "reinit_scoped", + ImplicitComponents_reinit_scoped + }, + { + "reinit_w_lifecycle", + ImplicitComponents_reinit_w_lifecycle + }, + { + "first_use_in_system", + ImplicitComponents_first_use_in_system + }, + { + "first_use_tag_in_system", + ImplicitComponents_first_use_tag_in_system + }, + { + "use_const", + ImplicitComponents_use_const + }, + { + "use_const_w_stage", + ImplicitComponents_use_const_w_stage + }, + { + "use_const_w_threads", + ImplicitComponents_use_const_w_threads + }, + { + "implicit_base", + ImplicitComponents_implicit_base + }, + { + "implicit_const", + ImplicitComponents_implicit_const + }, + { + "implicit_ref", + ImplicitComponents_implicit_ref + }, + { + "implicit_ptr", + ImplicitComponents_implicit_ptr + }, + { + "implicit_const_ref", + ImplicitComponents_implicit_const_ref + } +}; + +bake_test_case Snapshot_testcases[] = { + { + "simple_snapshot", + Snapshot_simple_snapshot + }, + { + "snapshot_iter", + Snapshot_snapshot_iter + } +}; + +bake_test_case WorldFactory_testcases[] = { + { + "entity", + WorldFactory_entity + }, + { + "entity_w_name", + WorldFactory_entity_w_name + }, + { + "entity_w_id", + WorldFactory_entity_w_id + }, + { + "prefab", + WorldFactory_prefab + }, + { + "prefab_w_name", + WorldFactory_prefab_w_name + }, + { + "type", + WorldFactory_type + }, + { + "type_w_name", + WorldFactory_type_w_name + }, + { + "system", + WorldFactory_system + }, + { + "system_w_name", + WorldFactory_system_w_name + }, + { + "system_w_expr", + WorldFactory_system_w_expr + }, + { + "query", + WorldFactory_query + }, + { + "query_w_expr", + WorldFactory_query_w_expr + }, + { + "snapshot", + WorldFactory_snapshot + }, + { + "module", + WorldFactory_module + }, + { + "module_w_name", + WorldFactory_module_w_name + } +}; + +bake_test_case World_testcases[] = { + { + "builtin_components", + World_builtin_components + }, + { + "multi_world_empty", + World_multi_world_empty + }, + { + "multi_world_component", + World_multi_world_component + }, + { + "multi_world_component_namespace", + World_multi_world_component_namespace + }, + { + "multi_world_module", + World_multi_world_module + }, + { + "type_id", + World_type_id + }, + { + "different_comp_same_name", + World_different_comp_same_name + }, + { + "reregister_after_reset", + World_reregister_after_reset + }, + { + "reregister_after_reset_w_namespace", + World_reregister_after_reset_w_namespace + }, + { + "reregister_namespace", + World_reregister_namespace + }, + { + "implicit_reregister_after_reset", + World_implicit_reregister_after_reset + }, + { + "reregister_after_reset_different_name", + World_reregister_after_reset_different_name + }, + { + "reimport", + World_reimport + }, + { + "reimport_module_after_reset", + World_reimport_module_after_reset + }, + { + "reimport_module_new_world", + World_reimport_module_new_world + }, + { + "reimport_namespaced_module", + World_reimport_namespaced_module + }, + { + "c_interop_module", + World_c_interop_module + }, + { + "c_interop_after_reset", + World_c_interop_after_reset + }, + { + "implicit_register_w_new_world", + World_implicit_register_w_new_world + }, + { + "count", + World_count + }, + { + "staged_count", + World_staged_count + }, + { + "async_stage_add", + World_async_stage_add + }, + { + "with_tag", + World_with_tag + }, + { + "with_tag_type", + World_with_tag_type + }, + { + "with_relation", + World_with_relation + }, + { + "with_relation_type", + World_with_relation_type + }, + { + "with_relation_object_type", + World_with_relation_object_type + }, + { + "with_tag_nested", + World_with_tag_nested + }, + { + "with_scope", + World_with_scope + }, + { + "with_scope_nested", + World_with_scope_nested + }, + { + "recursive_lookup", + World_recursive_lookup + }, + { + "type_w_tag_name", + World_type_w_tag_name + }, + { + "entity_w_tag_name", + World_entity_w_tag_name + }, + { + "template_component_name", + World_template_component_name + }, + { + "template_component_w_namespace_name", + World_template_component_w_namespace_name + }, + { + "template_component_w_same_namespace_name", + World_template_component_w_same_namespace_name + }, + { + "template_component_w_namespace_name_and_namespaced_arg", + World_template_component_w_namespace_name_and_namespaced_arg + }, + { + "template_component_w_same_namespace_name_and_namespaced_arg", + World_template_component_w_same_namespace_name_and_namespaced_arg + }, + { + "entity_as_tag", + World_entity_as_tag + }, + { + "entity_w_name_as_tag", + World_entity_w_name_as_tag + }, + { + "type_as_tag", + World_type_as_tag + }, + { + "entity_as_component", + World_entity_as_component + }, + { + "entity_w_name_as_component", + World_entity_w_name_as_component + }, + { + "entity_as_component_2_worlds", + World_entity_as_component_2_worlds + }, + { + "entity_as_namespaced_component_2_worlds", + World_entity_as_namespaced_component_2_worlds + }, + { + "entity_as_component_2_worlds_implicit_namespaced", + World_entity_as_component_2_worlds_implicit_namespaced + }, + { + "type_as_component", + World_type_as_component + }, + { + "type_w_name_as_component", + World_type_w_name_as_component + }, + { + "component_as_component", + World_component_as_component + } +}; + +bake_test_case Singleton_testcases[] = { + { + "set_get_singleton", + Singleton_set_get_singleton + }, + { + "get_mut_singleton", + Singleton_get_mut_singleton + }, + { + "emplace_singleton", + Singleton_emplace_singleton + }, + { + "modified_singleton", + Singleton_modified_singleton + }, + { + "patch_singleton", + Singleton_patch_singleton + }, + { + "add_singleton", + Singleton_add_singleton + }, + { + "remove_singleton", + Singleton_remove_singleton + }, + { + "has_singleton", + Singleton_has_singleton + }, + { + "singleton_system", + Singleton_singleton_system + }, + { + "get_singleton", + Singleton_get_singleton + }, + { + "type_id_from_world", + Singleton_type_id_from_world + } +}; + +bake_test_case Misc_testcases[] = { + { + "string_compare_w_char_ptr", + Misc_string_compare_w_char_ptr + }, + { + "string_compare_w_char_ptr_length_diff", + Misc_string_compare_w_char_ptr_length_diff + }, + { + "string_compare_w_string", + Misc_string_compare_w_string + }, + { + "string_view_compare_w_char_ptr", + Misc_string_view_compare_w_char_ptr + }, + { + "string_view_compare_w_string", + Misc_string_view_compare_w_string + }, + { + "string_compare_nullptr", + Misc_string_compare_nullptr + }, + { + "nullptr_string_compare", + Misc_nullptr_string_compare + }, + { + "nullptr_string_compare_nullptr", + Misc_nullptr_string_compare_nullptr + } +}; + +static bake_test_suite suites[] = { + { + "Entity", + NULL, + NULL, + 153, + Entity_testcases + }, + { + "Pairs", + NULL, + NULL, + 52, + Pairs_testcases + }, + { + "Switch", + NULL, + NULL, + 8, + Switch_testcases + }, + { + "Paths", + NULL, + NULL, + 12, + Paths_testcases + }, + { + "Type", + NULL, + NULL, + 15, + Type_testcases + }, + { + "System", + NULL, + NULL, + 42, + System_testcases + }, + { + "Trigger", + NULL, + NULL, + 6, + Trigger_testcases + }, + { + "Query", + NULL, + NULL, + 45, + Query_testcases + }, + { + "QueryBuilder", + NULL, + NULL, + 55, + QueryBuilder_testcases + }, + { + "FilterBuilder", + NULL, + NULL, + 62, + FilterBuilder_testcases + }, + { + "SystemBuilder", + NULL, + NULL, + 19, + SystemBuilder_testcases + }, + { + "Observer", + NULL, + NULL, + 7, + Observer_testcases + }, + { + "Filter", + NULL, + NULL, + 7, + Filter_testcases + }, + { + "ComponentLifecycle", + NULL, + NULL, + 41, + ComponentLifecycle_testcases + }, + { + "Refs", + NULL, + NULL, + 5, + Refs_testcases + }, + { + "Module", + NULL, + NULL, + 8, + Module_testcases + }, + { + "ImplicitComponents", + NULL, + NULL, + 26, + ImplicitComponents_testcases + }, + { + "Snapshot", + NULL, + NULL, + 2, + Snapshot_testcases + }, + { + "WorldFactory", + NULL, + NULL, + 15, + WorldFactory_testcases + }, + { + "World", + NULL, + NULL, + 49, + World_testcases + }, + { + "Singleton", + NULL, + NULL, + 11, + Singleton_testcases + }, + { + "Misc", + Misc_setup, + NULL, + 8, + Misc_testcases + } +}; + +int main(int argc, char *argv[]) { + ut_init(argv[0]); + return bake_test_run("cpp_api", argc, argv, suites, 22); +} diff --git a/fggl/ecs2/flecs/test/cpp_api/src/util.cpp b/fggl/ecs2/flecs/test/cpp_api/src/util.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0b1887f4552f79ca0c4f4acda42ace956a4744c1 --- /dev/null +++ b/fggl/ecs2/flecs/test/cpp_api/src/util.cpp @@ -0,0 +1,10 @@ +#include <cpp_api.h> + +void install_test_abort() { + ecs_os_set_api_defaults(); + ecs_os_api_t os_api = ecs_os_api; + os_api.abort_ = test_abort; + ecs_os_set_api(&os_api); + + ecs_tracing_enable(-5); +} diff --git a/fggl/ecs2/gfx/gfx.hpp b/fggl/ecs2/gfx/gfx.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6fa23d476cbead409894082af126313f818ad49d --- /dev/null +++ b/fggl/ecs2/gfx/gfx.hpp @@ -0,0 +1,10 @@ + +#include <fggl/ecs2/ecs.cpp> + +namespace fggl::ecs2::components { + class graphics { + public: + graphics(ecs2::World& ecs); + }; +} + diff --git a/fggl/gfx/ecs.hpp b/fggl/gfx/ecs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..46b6ed391da89f52d4041c311da984d5e5a76c1c --- /dev/null +++ b/fggl/gfx/ecs.hpp @@ -0,0 +1,44 @@ +#ifndef FGGL_GFX_ECS_H +#define FGGL_GFX_ECS_H + +#include <fggl/ecs2/ecs.hpp> +#include <fggl/math/types.hpp> + +#include <GL/gl.h> + +namespace fggl::components { + + struct GfxToken { + GLuint vao; + GLuint buffs[2]; + GLuint idxOffset; + GLuint idxSize; + GLuint pipeline; + }; + + struct GfxMat { + math::mat4 model; + math::mat4 view; + math::mat4 proj; + }; + + struct Camera { + math::vec3 target = math::vec3(0.0f, 0.0f, 0.0f); + float aspectRatio = 1280.0f / 720.0f; + float fov = glm::radians(45.0f); + float nearPlane = 0.1f; + float farPlane = 100.0f; + }; + + class Gfx { + public: + inline Gfx(ecs2::impl::world& world) { + world.module<Gfx>("gfx"); + world.component<GfxToken>("token"); + world.component<Camera>("camera"); + world.component<GfxMat>("mat"); + } + }; +}; + +#endif diff --git a/fggl/gfx/renderer.cpp b/fggl/gfx/renderer.cpp index a3febd7582606128fcb4d077c2e50b577d59f786..dde4510d0a5a4b9d59b0f0277a093e96e4c5c7a7 100644 --- a/fggl/gfx/renderer.cpp +++ b/fggl/gfx/renderer.cpp @@ -48,8 +48,8 @@ static GLuint createIndexBuffer(std::vector<uint32_t>& indexData) { return buffId; } -GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) { - GlRenderToken token; +MeshRenderer::token_t MeshRenderer::upload(fggl::data::Mesh& mesh) { + MeshRenderer::token_t token; glGenVertexArrays(1, &token.vao); glBindVertexArray(token.vao); @@ -70,7 +70,7 @@ GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) { void MeshRenderer::render(const Window& window, const fggl::ecs::ECS& ecs, const ecs::entity_t camera, float dt) { if ( camera == ecs::NULL_ENTITY ) return; - auto entities = ecs.getEntityWith<GlRenderToken>(); + auto entities = ecs.getEntityWith<MeshRenderer::token_t>(); if ( entities.size() == 0 ) return; total += dt; @@ -96,7 +96,7 @@ void MeshRenderer::render(const Window& window, const fggl::ecs::ECS& ecs, const // TODO the nvidia performance presentation said I shouldn't use uniforms for large data for ( auto& entity : entities ) { const auto& transform = ecs.getComponent<fggl::math::Transform>(entity); - const auto& mesh = ecs.getComponent<GlRenderToken>(entity); + const auto& mesh = ecs.getComponent<MeshRenderer::token_t>(entity); glm::mat4 model = transform->model(); // model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f), glm::vec3(0.0f,1.0f,0.0f)); diff --git a/fggl/gfx/renderer.hpp b/fggl/gfx/renderer.hpp index 1772e2f4e6ad4d8a00c121399896be08ab9c29e1..e606cf9769c4f6baa04a22fc0cbd692e3d881ac0 100644 --- a/fggl/gfx/renderer.hpp +++ b/fggl/gfx/renderer.hpp @@ -7,19 +7,13 @@ #include <fggl/ecs/ecs.hpp> #include <fggl/gfx/ogl.hpp> -namespace fggl::gfx { +#include <fggl/gfx/ecs.hpp> - struct GlRenderToken { - GLuint vao; - GLuint buffs[2]; - GLuint idxOffset; - GLuint idxSize; - GLuint pipeline; - }; +namespace fggl::gfx { class GlMeshRenderer { public: - using token_t = GlRenderToken; + using token_t = fggl::components::GfxToken; token_t upload(fggl::data::Mesh& mesh); @@ -32,7 +26,7 @@ namespace fggl::gfx { // specialisation hooks using Graphics = GlGraphics; using MeshRenderer = GlMeshRenderer; - using MeshToken = GlRenderToken; + using MeshToken = fggl::components::GfxToken; }; diff --git a/fggl/math/ecs.hpp b/fggl/math/ecs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..00a706d6c0e81132e5151f1ea84dcc9870179994 --- /dev/null +++ b/fggl/math/ecs.hpp @@ -0,0 +1,15 @@ +#include <fggl/ecs2/ecs.hpp> +#include <fggl/math/types.hpp> + +namespace fggl::components { + + using fggl::math::Transform; + + class Math { + public: + inline Math(fggl::ecs2::impl::world& ecs) { + ecs.module<Math>(); + ecs.component<Transform>(); + } + }; +}