diff --git a/.gitignore b/.gitignore index 796b96d..a6c2198 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/.cache /build diff --git a/.vscode/launch.json b/.vscode/launch.json index 815a672..4fe56f3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,7 @@ "configurations": [ { "name": "Launch Tests (Windows)", - "type": "cppvsdbg", + "type": "lldb", "request": "launch", "program": "${workspaceFolder}/build/tests/Tecs-tests.exe", "args": [], @@ -14,7 +14,7 @@ }, { "name": "Launch Benchmark (Windows)", - "type": "cppvsdbg", + "type": "lldb", "request": "launch", "program": "${workspaceFolder}/build/tests/Tecs-benchmark.exe", "args": [], @@ -25,7 +25,7 @@ }, { "name": "Launch Benchmark Tracy (Windows)", - "type": "cppvsdbg", + "type": "lldb", "request": "launch", "program": "${workspaceFolder}/build/tests/Tecs-benchmark-tracy.exe", "args": [], @@ -34,6 +34,61 @@ "environment": [], "console": "integratedTerminal" }, + { + "name": "Launch Benchmark C-ABI (Windows)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/build/tests/Tecs-benchmark-cabi.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build/tests/", + "environment": [], + "console": "integratedTerminal" + }, + { + "name": "Launch Benchmark C-ABI (Linux)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/build/tests/Tecs-benchmark-cabi", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build/tests/", + "environment": [], + "console": "integratedTerminal" + }, + { + "name": "Launch Benchmark Tracy C-ABI (Windows)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/build/tests/Tecs-benchmark-tracy-cabi.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build/tests/", + "environment": [], + "console": "integratedTerminal" + }, + { + "name": "Launch C-ABI Tests (Windows)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/build/tests/Tecs-c_abi_test.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build/tests/", + "environment": [], + "console": "integratedTerminal" + }, + { + "name": "Launch C-ABI Tests (Linux)", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/build/tests/Tecs-c_abi_test", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build/tests/", + "environment": [], + "console": "integratedTerminal" + }, { "name": "Launch Tests (Linux)", "type": "cppdbg", diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ed525b..80d62a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,8 @@ if(TECS_UNCHECKED_MODE) target_compile_definitions(${PROJECT_NAME} INTERFACE TECS_UNCHECKED_MODE) endif() +include(gen_c_abi.cmake) + if(UNIX AND NOT ANDROID) target_link_libraries( ${PROJECT_NAME} diff --git a/examples/tracing_example.cpp b/examples/tracing_example.cpp index 14621eb..612d61b 100644 --- a/examples/tracing_example.cpp +++ b/examples/tracing_example.cpp @@ -1,7 +1,6 @@ #include "complex_component.hh" #include "components.hh" -#include #include #include @@ -124,9 +123,7 @@ int main(int /* argc */, char ** /* argv */) { for (auto &[id, name] : threadNames) { trace.SetThreadName(name, id); } - std::ofstream traceFile("example-trace.csv"); - trace.SaveToCSV(traceFile); - traceFile.close(); + trace.SaveToCSV("example-trace.csv"); return 0; } diff --git a/gen_c_abi.cmake b/gen_c_abi.cmake new file mode 100644 index 0000000..25fc6e9 --- /dev/null +++ b/gen_c_abi.cmake @@ -0,0 +1,90 @@ +function(TecsGenerateCHeaders) + set(options OBJECT_TARGET) + set(oneValueArgs TARGET_NAME HEADER_OUTPUT_DIR ECS_INCLUDE_PATH ECS_IMPL_PATH ECS_NAME COMPONENT_TYPE_PREFIX) + set(multiValueArgs SOURCES LINK_LIBRARIES INCLUDE_DIRECTORIES COMPILE_DEFINITIONS) + cmake_parse_arguments(PARSE_ARGV 0 arg "${options}" "${oneValueArgs}" "${multiValueArgs}") + + include(CheckIPOSupported) + check_ipo_supported(RESULT lto_supported OUTPUT lto_error) + + set(TECS_PROJECT_ROOT ${CMAKE_CURRENT_FUNCTION_LIST_DIR}) + + add_executable(${arg_TARGET_NAME}-codegen ${TECS_PROJECT_ROOT}/src/c_abi/codegen/gen_main.cc) + target_link_libraries(${arg_TARGET_NAME}-codegen PRIVATE ${arg_LINK_LIBRARIES}) + target_include_directories( + ${arg_TARGET_NAME}-codegen + PRIVATE + ${TECS_PROJECT_ROOT}/inc + ${arg_INCLUDE_DIRECTORIES} + ) + target_compile_definitions(${arg_TARGET_NAME}-codegen PRIVATE + TECS_C_ABI_ECS_NAME=${arg_ECS_NAME} + TECS_C_ABI_TYPE_PREFIX="${arg_COMPONENT_TYPE_PREFIX}" + TECS_SHARED_INTERNAL + ${arg_COMPILE_DEFINITIONS} + ) + set_target_properties(${arg_TARGET_NAME}-codegen PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + + if(DEFINED arg_ECS_INCLUDE_PATH) + target_compile_definitions(${arg_TARGET_NAME}-codegen PRIVATE + TECS_C_ABI_ECS_INCLUDE="${arg_ECS_INCLUDE_PATH}" + ) + endif() + + if(DEFINED arg_ECS_IMPL_PATH) + target_compile_definitions(${arg_TARGET_NAME}-codegen PRIVATE + TECS_C_ABI_ECS_IMPL_INCLUDE="${arg_ECS_IMPL_PATH}" + ) + endif() + + add_custom_command( + OUTPUT + ${arg_HEADER_OUTPUT_DIR}/Tecs_gen.h + ${CMAKE_CURRENT_BINARY_DIR}/${arg_TARGET_NAME}/Tecs_gen.cc + COMMAND + ${arg_TARGET_NAME}-codegen + ${arg_HEADER_OUTPUT_DIR}/Tecs_gen.h + ${CMAKE_CURRENT_BINARY_DIR}/${arg_TARGET_NAME}/Tecs_gen.cc + DEPENDS ${arg_TARGET_NAME}-codegen + ) + + add_custom_target(${arg_TARGET_NAME}-codegen-output + DEPENDS + ${arg_HEADER_OUTPUT_DIR}/Tecs_gen.h + ${CMAKE_CURRENT_BINARY_DIR}/${arg_TARGET_NAME}/Tecs_gen.cc + ) + + set(BUILD_FILE_LIST + ${TECS_PROJECT_ROOT}/src/c_abi/Tecs_entity_view.cc + ${TECS_PROJECT_ROOT}/src/c_abi/Tecs_tracing.cc + ${CMAKE_CURRENT_BINARY_DIR}/${arg_TARGET_NAME}/Tecs_gen.cc + ${arg_SOURCES} + ) + + if(arg_OBJECT_TARGET) + add_library(${arg_TARGET_NAME} OBJECT ${BUILD_FILE_LIST}) + else() + add_library(${arg_TARGET_NAME} SHARED ${BUILD_FILE_LIST}) + endif() + target_link_libraries(${arg_TARGET_NAME} PRIVATE Tecs ${arg_LINK_LIBRARIES}) + target_include_directories( + ${arg_TARGET_NAME} + PUBLIC + ${TECS_PROJECT_ROOT}/inc + ${arg_HEADER_OUTPUT_DIR} + PRIVATE + ${arg_INCLUDE_DIRECTORIES} + ) + add_dependencies(${arg_TARGET_NAME} ${arg_TARGET_NAME}-codegen-output) + target_compile_definitions(${arg_TARGET_NAME} PRIVATE TECS_SHARED_INTERNAL ${arg_COMPILE_DEFINITIONS}) + if(lto_supported) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "IPO / LTO enabled") + set_target_properties(${arg_TARGET_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + else() + message(STATUS "IPO / LTO not supported: ${lto_error}") + endif() +endfunction() diff --git a/inc/Tecs.hh b/inc/Tecs.hh index 670c56c..ff3ed3e 100644 --- a/inc/Tecs.hh +++ b/inc/Tecs.hh @@ -80,18 +80,22 @@ namespace Tecs { return (TECS_ENTITY_ECS_IDENTIFIER_TYPE)ecsId; } + inline uint64_t GetNextTransactionId() const { + return nextTransactionId; + } + /** * Returns the index of a Component type for use in a bitset. */ template - inline static constexpr size_t GetComponentIndex() { + inline static constexpr uint32_t GetComponentIndex() { return GetComponentIndex<0, U>(); } /** * Returns the number of Component types registered in this ECS instance. */ - inline static constexpr size_t GetComponentCount() { + inline static constexpr uint32_t GetComponentCount() { return sizeof...(Tn); } @@ -107,6 +111,13 @@ namespace Tecs { } } + /** + * Returns the registered name of the Nth Component type, or a default of "ComponentN" if none is set. + */ + inline static std::string GetComponentName(uint32_t componentIndex) { + return GetComponentName(componentIndex); + } + /** * Returns true if the Component type is part of this ECS. */ @@ -120,17 +131,29 @@ namespace Tecs { } private: - template - inline static constexpr size_t GetComponentIndex() { + template + inline static constexpr uint32_t GetComponentIndex() { static_assert(I < sizeof...(Tn), "Component does not exist"); - if constexpr (std::is_same>::type>::value) { + if constexpr (std::is_same>>()) { return I; } else { return GetComponentIndex(); } } + template + inline static std::string GetComponentName(uint32_t index) { + if (index == 0) { + return GetComponentName(); + } + if constexpr (sizeof...(Un) > 0) { + return GetComponentName(index - 1); + } else { + throw std::runtime_error("Component does not exist"); + } + } + using ComponentBitset = std::bitset<1 + sizeof...(Tn)>; struct EntityMetadata : public ComponentBitset { @@ -162,15 +185,13 @@ namespace Tecs { #endif #ifndef TECS_HEADER_ONLY - size_t ecsId; + uint64_t ecsId; #endif template friend class Lock; - template + template friend class Transaction; - template typename, typename...> - friend class BaseTransaction; friend struct Entity; }; } // namespace Tecs diff --git a/inc/Tecs_entity.hh b/inc/Tecs_entity.hh index 1f26b7e..d93238f 100644 --- a/inc/Tecs_entity.hh +++ b/inc/Tecs_entity.hh @@ -2,6 +2,7 @@ #include "Tecs_permissions.hh" +#include #include #include #include @@ -56,26 +57,25 @@ namespace Tecs { struct Entity { // Workaround for Clang so that std::atomic operations can be inlined as if uint64. See issue: // https://stackoverflow.com/questions/60445848/clang-doesnt-inline-stdatomicload-for-loading-64-bit-structs - alignas(sizeof(TECS_ENTITY_GENERATION_TYPE) + - sizeof(TECS_ENTITY_INDEX_TYPE)) TECS_ENTITY_GENERATION_TYPE generation; - TECS_ENTITY_INDEX_TYPE index; + alignas(sizeof(TECS_ENTITY_GENERATION_TYPE) + sizeof(TECS_ENTITY_INDEX_TYPE)) TECS_ENTITY_INDEX_TYPE index; + TECS_ENTITY_GENERATION_TYPE generation; - inline Entity() : generation(0), index(0) {} + inline Entity() : index(0), generation(0) {} inline Entity(uint64_t eid) - : generation(eid >> (sizeof(TECS_ENTITY_INDEX_TYPE) * 8)), - index(eid & std::numeric_limits::max()) {} + : index(eid & std::numeric_limits::max()), + generation(eid >> (sizeof(TECS_ENTITY_INDEX_TYPE) * 8)) {} inline Entity(TECS_ENTITY_INDEX_TYPE index, TECS_ENTITY_GENERATION_TYPE generation) - : generation(generation), index(index) {} + : index(index), generation(generation) {} inline Entity(TECS_ENTITY_INDEX_TYPE index, TECS_ENTITY_GENERATION_TYPE generation, TECS_ENTITY_ECS_IDENTIFIER_TYPE ecsId) - : generation(GenerationWithIdentifier(generation, ecsId)), index(index) {} + : index(index), generation(GenerationWithIdentifier(generation, ecsId)) {} public: template inline bool Exists(const LockType &lock) const { - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; if (index >= metadataList.size()) return false; auto &metadata = metadataList[index]; @@ -93,8 +93,8 @@ namespace Tecs { template inline bool Has(const LockType &lock) const { static_assert(!contains_global_components(), "Entities cannot have global components"); - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; if (index >= metadataList.size()) return false; auto &metadata = metadataList[index]; @@ -102,6 +102,18 @@ namespace Tecs { lock.instance.template BitsetHas(metadata); } + template::digits), int> = 0> + inline bool HasBitset(const LockType &lock, + const std::bitset<1 + LockType::ECS::GetComponentCount()> &componentBits) const { + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; + if (index >= metadataList.size()) return false; + + auto &metadata = metadataList[index]; + return metadata[0] && metadata.generation == generation && (metadata & componentBits) == componentBits; + } + template inline bool Had(const LockType &lock) const { static_assert(!contains_global_components(), "Entities cannot have global components"); @@ -112,6 +124,16 @@ namespace Tecs { lock.instance.template BitsetHas(metadata); } + template::digits), int> = 0> + inline bool HadBitset(const LockType &lock, + const std::bitset<1 + LockType::ECS::GetComponentCount()> &componentBits) const { + if (index >= lock.instance.metadata.readComponents.size()) return false; + + auto &metadata = lock.instance.metadata.readComponents[index]; + return metadata[0] && metadata.generation == generation && (metadata & componentBits) == componentBits; + } + template, LockType>::value, T, const T>> @@ -123,8 +145,8 @@ namespace Tecs { static_assert(!is_global_component(), "Global components must be accessed through lock.Get()"); #ifndef TECS_UNCHECKED_MODE - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; if (index >= metadataList.size()) { throw std::runtime_error("Entity does not exist: " + std::to_string(*this)); } @@ -139,12 +161,12 @@ namespace Tecs { if constexpr (is_add_remove_allowed() && !std::is_const()) { #ifdef TECS_UNCHECKED_MODE - auto &metadataList = lock.permissions[0] ? lock.instance.metadata.writeComponents - : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; auto &metadata = metadataList[index]; #endif if (!lock.instance.template BitsetHas(metadata)) { - lock.base->writeAccessedFlags[0] = true; + lock.transaction->template SetAccessFlag(); lock.instance.metadata.AccessEntity(index); // Reset value before allowing reading. @@ -161,8 +183,8 @@ namespace Tecs { #endif } - if constexpr (!std::is_const()) lock.base->template SetAccessFlag(index); - if (lock.instance.template BitsetHas(lock.permissions)) { + if constexpr (!std::is_const()) lock.transaction->template SetAccessFlag(index); + if (lock.instance.template BitsetHas(lock.writePermissions)) { return storage.writeComponents[index]; } else { return storage.readComponents[index]; @@ -200,8 +222,8 @@ namespace Tecs { static_assert(!is_global_component(), "Global components must be accessed through lock.Set()"); #ifndef TECS_UNCHECKED_MODE - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; if (index >= metadataList.size()) { throw std::runtime_error("Entity does not exist: " + std::to_string(*this)); } @@ -214,12 +236,12 @@ namespace Tecs { if constexpr (is_add_remove_allowed()) { #ifdef TECS_UNCHECKED_MODE - auto &metadataList = lock.permissions[0] ? lock.instance.metadata.writeComponents - : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; auto &metadata = metadataList[index]; #endif if (!lock.instance.template BitsetHas(metadata)) { - lock.base->writeAccessedFlags[0] = true; + lock.transaction->template SetAccessFlag(); lock.instance.metadata.AccessEntity(index); metadata[1 + lock.instance.template GetComponentIndex()] = true; @@ -233,7 +255,7 @@ namespace Tecs { #endif } - lock.base->template SetAccessFlag(index); + lock.transaction->template SetAccessFlag(index); return lock.instance.template Storage().writeComponents[index] = value; } @@ -243,8 +265,8 @@ namespace Tecs { static_assert(!is_global_component(), "Global components must be accessed through lock.Set()"); #ifndef TECS_UNCHECKED_MODE - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; if (index >= metadataList.size()) { throw std::runtime_error("Entity does not exist: " + std::to_string(*this)); } @@ -257,12 +279,12 @@ namespace Tecs { if constexpr (is_add_remove_allowed()) { #ifdef TECS_UNCHECKED_MODE - auto &metadataList = lock.permissions[0] ? lock.instance.metadata.writeComponents - : lock.instance.metadata.readComponents; + auto &metadataList = lock.writePermissions[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; auto &metadata = metadataList[index]; #endif if (!lock.instance.template BitsetHas(metadata)) { - lock.base->writeAccessedFlags[0] = true; + lock.transaction->template SetAccessFlag(); lock.instance.metadata.AccessEntity(index); metadata[1 + lock.instance.template GetComponentIndex()] = true; @@ -276,7 +298,7 @@ namespace Tecs { #endif } - lock.base->template SetAccessFlag(index); + lock.transaction->template SetAccessFlag(index); return lock.instance.template Storage().writeComponents[index] = T(std::forward(args)...); } @@ -319,7 +341,7 @@ namespace Tecs { // Invalidate the entity and all of its Components lock.RemoveAllComponents(copy); - lock.base->writeAccessedFlags[0] = true; + lock.transaction->template SetAccessFlag(); lock.instance.metadata.AccessEntity(copy); lock.instance.metadata.writeComponents[copy][0] = false; size_t validIndex = lock.instance.metadata.validEntityIndexes[copy]; diff --git a/inc/Tecs_entity_view.hh b/inc/Tecs_entity_view.hh index 5c393f5..28212a7 100644 --- a/inc/Tecs_entity_view.hh +++ b/inc/Tecs_entity_view.hh @@ -165,9 +165,8 @@ namespace Tecs { return EntityView(*storage, start_index + offset, std::min(end_index, start_index + offset + count)); } - private: const std::vector *storage = nullptr; size_t start_index = 0; size_t end_index = 0; }; -}; // namespace Tecs +} // namespace Tecs diff --git a/inc/Tecs_lock.hh b/inc/Tecs_lock.hh index dacdd66..534c20f 100644 --- a/inc/Tecs_lock.hh +++ b/inc/Tecs_lock.hh @@ -47,28 +47,32 @@ namespace Tecs { template typename ECSType, typename... AllComponentTypes, typename... Permissions> class Lock, Permissions...> { private: + using PermissionBitset = std::bitset<1 + sizeof...(AllComponentTypes)>; using ECS = ECSType; using LockType = Lock; ECS &instance; - std::shared_ptr> base; - std::bitset<1 + sizeof...(AllComponentTypes)> permissions; + std::shared_ptr> transaction; + PermissionBitset writePermissions; // Private constructor for DynamicLock to Lock conversion template - inline Lock(ECS &instance, decltype(base) base, decltype(permissions) permissions) - : instance(instance), base(base), permissions(permissions) {} + inline Lock(ECS &instance, decltype(transaction) transaction, PermissionBitset writePermissions) + : instance(instance), transaction(transaction), writePermissions(writePermissions) {} public: // Start a new transaction inline Lock(ECS &instance) : instance(instance) { - base = std::make_shared>(instance); - permissions[0] = is_add_remove_allowed(); + PermissionBitset readPermissions; + readPermissions[0] = true; + writePermissions[0] = is_add_remove_allowed(); // clang-format off (( - permissions[1 + instance.template GetComponentIndex()] = is_write_allowed() + readPermissions[1 + instance.template GetComponentIndex()] = is_read_allowed(), + writePermissions[1 + instance.template GetComponentIndex()] = is_write_allowed() ), ...); // clang-format on + transaction = std::make_shared>(instance, readPermissions, writePermissions); } // Returns true if this lock type can be constructed from a lock with the specified source permissions @@ -91,15 +95,24 @@ namespace Tecs { // Reference an existing transaction template(), int> = 0> inline Lock(const Lock &source) - : instance(source.instance), base(source.base), permissions(source.permissions) {} + : instance(source.instance), transaction(source.transaction), writePermissions(source.writePermissions) {} + + template + inline bool IsWriteAllowed() const { + return writePermissions[1 + instance.template GetComponentIndex()]; + } + + inline bool IsAddRemoveAllowed() const { + return writePermissions[0]; + } inline constexpr ECS &GetInstance() const { return instance; } #ifndef TECS_HEADER_ONLY - inline size_t GetTransactionId() const { - return base->transactionId; + inline uint64_t GetTransactionId() const { + return transaction->GetTransactionId(); } #endif @@ -114,7 +127,7 @@ namespace Tecs { inline const EntityView EntitiesWith() const { static_assert(!is_global_component(), "Entities can't have global components"); - if (permissions[0]) { + if (IsAddRemoveAllowed()) { return instance.template Storage().writeValidEntities; } else { return instance.template Storage().readValidEntities; @@ -126,7 +139,7 @@ namespace Tecs { } inline const EntityView Entities() const { - if (permissions[0]) { + if (IsAddRemoveAllowed()) { return instance.metadata.writeValidEntities; } else { return instance.metadata.readValidEntities; @@ -140,7 +153,7 @@ namespace Tecs { */ inline Entity NewEntity() const { static_assert(is_add_remove_allowed(), "Lock does not have AddRemove permission."); - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(); Entity entity; if (instance.freeEntities.empty()) { @@ -179,7 +192,7 @@ namespace Tecs { inline bool Has() const { static_assert(all_global_components(), "Only global components can be accessed without an Entity"); - if (permissions[0]) { + if (IsAddRemoveAllowed()) { return instance.template BitsetHas(instance.globalWriteMetadata); } else { return instance.template BitsetHas(instance.globalReadMetadata); @@ -193,6 +206,24 @@ namespace Tecs { return instance.template BitsetHas(instance.globalReadMetadata); } + template, LockType>::value, T, const T>> + inline ReturnType *GetStorage() const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + static_assert(is_write_allowed() || std::is_const(), + "Can't get non-const reference of read only Component."); + + if (!std::is_const()) transaction->template SetAccessFlag(true); + + auto &storage = instance.template Storage(); + if (instance.template BitsetHas(writePermissions)) { + return storage.writeComponents.data(); + } else { + return storage.readComponents.data(); + } + } + template, LockType>::value, T, const T>> inline ReturnType &Get() const { @@ -203,16 +234,16 @@ namespace Tecs { static_assert(is_global_component(), "Only global components can be accessed without an Entity"); #ifndef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif auto &storage = instance.template Storage(); if constexpr (is_add_remove_allowed()) { #ifdef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if (!instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(); metadata[1 + instance.template GetComponentIndex()] = true; storage.writeComponents.resize(1); @@ -225,14 +256,22 @@ namespace Tecs { #endif } - if (!std::is_const()) base->template SetAccessFlag(); - if (instance.template BitsetHas(permissions)) { + if (!std::is_const()) transaction->template SetAccessFlag(); + if (instance.template BitsetHas(writePermissions)) { return storage.writeComponents[0]; } else { return storage.readComponents[0]; } } + template + inline const T *GetPreviousStorage() const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + + return instance.template Storage().readComponents.data(); + } + template inline const T &GetPrevious() const { using CompType = std::remove_cv_t; @@ -253,15 +292,15 @@ namespace Tecs { static_assert(is_global_component(), "Only global components can be accessed without an Entity"); #ifndef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if constexpr (is_add_remove_allowed()) { #ifdef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if (!instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(); metadata[1 + instance.template GetComponentIndex()] = true; instance.template Storage().writeComponents.resize(1); @@ -272,7 +311,7 @@ namespace Tecs { #endif } - base->template SetAccessFlag(); + transaction->template SetAccessFlag(); return instance.template Storage().writeComponents[0] = value; } @@ -282,15 +321,15 @@ namespace Tecs { static_assert(is_global_component(), "Only global components can be accessed without an Entity"); #ifndef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if constexpr (is_add_remove_allowed()) { #ifdef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if (!instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(); metadata[1 + instance.template GetComponentIndex()] = true; instance.template Storage().writeComponents.resize(1); @@ -301,7 +340,7 @@ namespace Tecs { #endif } - base->template SetAccessFlag(); + transaction->template SetAccessFlag(); return instance.template Storage().writeComponents[0] = T(std::forward(args)...); } @@ -372,18 +411,18 @@ namespace Tecs { inline auto ReadOnlySubset() const { using NewLockType = Lock::type_readonly>; static_assert(has_permissions(), "Lock types are not a subset of existing permissions."); - return NewLockType(this->instance, this->base, {}); + return NewLockType(this->instance, this->transaction, {}); } long UseCount() const { - return base.use_count(); + return transaction.use_count(); } private: template inline void AllocateComponents(size_t count) const { if constexpr (!is_global_component()) { - base->template SetAccessFlag(); + transaction->template SetAccessFlag(); auto &storage = instance.template Storage(); size_t newSize = storage.writeComponents.size() + count; @@ -399,9 +438,9 @@ namespace Tecs { if constexpr (!is_global_component()) { // Ignore global components auto &metadata = instance.metadata.writeComponents[index]; if (instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(); + transaction->template SetAccessFlag(index); instance.metadata.AccessEntity(index); - base->template SetAccessFlag(index); metadata[1 + instance.template GetComponentIndex()] = false; auto &compIndex = instance.template Storage(); @@ -418,8 +457,8 @@ namespace Tecs { auto &metadata = instance.globalWriteMetadata; if (instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; - base->template SetAccessFlag(); + transaction->template SetAccessFlag(); + transaction->template SetAccessFlag(); metadata[1 + instance.template GetComponentIndex()] = false; instance.template Storage().writeComponents[0] = {}; @@ -437,6 +476,12 @@ namespace Tecs { friend struct Entity; }; + template + struct is_lock : std::false_type {}; + + template + struct is_lock> : std::true_type {}; + template struct is_dynamic_lock : std::false_type {}; @@ -446,16 +491,25 @@ namespace Tecs { template typename ECSType, typename... AllComponentTypes, typename... StaticPermissions> class DynamicLock, StaticPermissions...> : public Lock, StaticPermissions...> { + public: + using PermissionBitset = std::bitset<1 + sizeof...(AllComponentTypes)>; + private: using ECS = ECSType; using BaseLockType = Lock; - const std::bitset<1 + sizeof...(AllComponentTypes)> readPermissions; + const PermissionBitset readPermissions; + + // Private constructor for DynamicLock to DynamicLock conversion in TryLock + template + inline DynamicLock(ECS &instance, decltype(BaseLockType::transaction) transaction, + PermissionBitset readPermissions, PermissionBitset writePermissions) + : BaseLockType(instance, transaction, writePermissions), readPermissions(readPermissions) {} template - static inline constexpr auto generateReadBitset() { + static inline auto generateReadBitset() { std::bitset<1 + sizeof...(AllComponentTypes)> result; - if constexpr (sizeof...(AllComponentTypes) < 64) { + if constexpr (sizeof...(AllComponentTypes) <= std::numeric_limits::digits) { // clang-format off constexpr uint64_t mask = 1 | (( ((uint64_t)is_read_allowed()) @@ -484,7 +538,7 @@ namespace Tecs { template static inline constexpr auto generateWriteBitset() { std::bitset<1 + sizeof...(AllComponentTypes)> result; - if constexpr (sizeof...(AllComponentTypes) < 64) { + if constexpr (sizeof...(AllComponentTypes) <= std::numeric_limits::digits) { // clang-format off constexpr uint64_t mask = (uint64_t)Tecs::is_add_remove_allowed() | (( ((uint64_t)is_write_allowed()) @@ -506,17 +560,30 @@ namespace Tecs { template DynamicLock(const LockType &lock) : BaseLockType(lock), readPermissions(generateReadBitset(lock)) {} + // Start a new transaction from runtime permissions + inline DynamicLock(ECS &instance, const PermissionBitset &readPermissions, + const PermissionBitset &writePermissions) + : Lock(instance, std::make_shared>(instance, readPermissions, writePermissions), + writePermissions), + readPermissions(readPermissions) {} + template - std::optional> TryLock() const { - using DynamicLockType = Lock; + std::optional> TryLock() const { + using DynamicLockType = DynamicLock; if constexpr (BaseLockType::template has_permissions()) { - return DynamicLockType(this->instance, this->base, this->permissions); + return DynamicLockType(this->instance, + this->transaction, + this->readPermissions, + this->writePermissions); } else { - static constexpr auto requestedRead = generateReadBitset(); - static constexpr auto requestedWrite = generateWriteBitset(); + static const auto requestedRead = generateReadBitset(); + static const auto requestedWrite = generateWriteBitset(); if ((requestedRead & readPermissions) == requestedRead && - (requestedWrite & this->permissions) == requestedWrite) { - return DynamicLockType(this->instance, this->base, this->permissions); + (requestedWrite & this->writePermissions) == requestedWrite) { + return DynamicLockType(this->instance, + this->transaction, + this->readPermissions, + this->writePermissions); } return {}; } diff --git a/inc/Tecs_observer.hh b/inc/Tecs_observer.hh index 0166692..85af536 100644 --- a/inc/Tecs_observer.hh +++ b/inc/Tecs_observer.hh @@ -114,9 +114,12 @@ namespace Tecs { if (!writeQueue) writeQueue = std::make_shared>(); } + bool IsEmpty() const { + return observers.empty(); + } + template void AddEvent(Args &&...args) { - if (observers.empty()) return; writeQueue->emplace_back(std::forward(args)...); } diff --git a/inc/Tecs_permissions.hh b/inc/Tecs_permissions.hh index 120f7c8..e6e3cf6 100644 --- a/inc/Tecs_permissions.hh +++ b/inc/Tecs_permissions.hh @@ -12,9 +12,14 @@ namespace Tecs { class Lock {}; template class DynamicLock {}; - template + template class Transaction {}; + namespace abi { + template + class Lock {}; + } // namespace abi + /** * Lock permissions are passed in as template arguments when creating a Transaction or Lock. * @@ -88,7 +93,7 @@ namespace Tecs { #define TECS_NAME_COMPONENT(ComponentType, ComponentName) \ template<> \ struct Tecs::component_name { \ - static constexpr char value[] = (ComponentName); \ + static constexpr char value[] = ComponentName; \ }; // contains::value is true if T is part of the set Un... @@ -120,6 +125,10 @@ namespace Tecs { struct is_read_allowed> : std::disjunction...> {}; template struct is_read_allowed> : std::disjunction...> {}; + template + struct is_read_allowed> : std::disjunction...> {}; + template + struct is_read_allowed> : std::disjunction...> {}; template struct is_write_allowed> : std::disjunction...> {}; @@ -129,6 +138,10 @@ namespace Tecs { struct is_write_allowed> : std::disjunction...> {}; template struct is_write_allowed> : std::disjunction...> {}; + template + struct is_write_allowed> : std::disjunction...> {}; + template + struct is_write_allowed> : std::disjunction...> {}; template struct is_add_remove_allowed> : contains {}; @@ -138,6 +151,10 @@ namespace Tecs { struct is_add_remove_allowed> : contains {}; template struct is_add_remove_allowed> : contains {}; + template + struct is_add_remove_allowed> : contains {}; + template + struct is_add_remove_allowed> : contains {}; // clang-format on // Check SubLock <= Lock for component type T diff --git a/inc/Tecs_storage.hh b/inc/Tecs_storage.hh index d2d3439..77824d6 100644 --- a/inc/Tecs_storage.hh +++ b/inc/Tecs_storage.hh @@ -21,6 +21,8 @@ static_assert(ATOMIC_INT_LOCK_FREE == 2, "std::atomic_int is not lock-free"); +typedef void tecs_lock_t; + namespace Tecs { template class ComponentIndex { @@ -359,10 +361,8 @@ namespace Tecs { template friend class Lock; - template + template friend class Transaction; - template typename, typename...> - friend class BaseTransaction; friend struct Entity; }; } // namespace Tecs diff --git a/inc/Tecs_tracing.hh b/inc/Tecs_tracing.hh index b9cd2f5..4ac9281 100644 --- a/inc/Tecs_tracing.hh +++ b/inc/Tecs_tracing.hh @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -16,19 +17,6 @@ #define TECS_PERFORMANCE_TRACING_MAX_EVENTS 10000 #endif -#ifndef TECS_EXTERNAL_TRACE_TRANSACTION_STARTING - #define TECS_EXTERNAL_TRACE_TRANSACTION_STARTING(permissions) -#endif -#ifndef TECS_EXTERNAL_TRACE_TRANSACTION_STARTED - #define TECS_EXTERNAL_TRACE_TRANSACTION_STARTED(permissions) -#endif -#ifndef TECS_EXTERNAL_TRACE_TRANSACTION_ENDING - #define TECS_EXTERNAL_TRACE_TRANSACTION_ENDING(permissions) -#endif -#ifndef TECS_EXTERNAL_TRACE_TRANSACTION_ENDED - #define TECS_EXTERNAL_TRACE_TRANSACTION_ENDED(permissions) -#endif - namespace Tecs { struct TraceEvent { enum class Type { @@ -74,14 +62,30 @@ namespace Tecs { nonstd::span metadataEvents; std::vector> componentEvents; std::vector componentNames; - std::map threadNames; + std::map threadNames; + + void SetThreadName(const std::string &name, size_t threadIdHash) { + threadNames[threadIdHash] = name; + } - void SetThreadName(std::string name, std::thread::id threadId = std::this_thread::get_id()) { - threadNames[threadId] = name; + void SetThreadName(const std::string &name, std::thread::id threadId = std::this_thread::get_id()) { + static std::hash threadHasher; + SetThreadName(name, threadHasher(threadId)); + } + + std::string GetThreadName(size_t threadIdHash) { + auto it = threadNames.find(threadIdHash); + if (it != threadNames.end()) { + return it->second; + } + std::stringstream ss; + ss << threadIdHash; + return ss.str(); } std::string GetThreadName(std::thread::id threadId = std::this_thread::get_id()) { - auto it = threadNames.find(threadId); + static std::hash threadHasher; + auto it = threadNames.find(threadHasher(threadId)); if (it != threadNames.end()) { return it->second; } @@ -90,7 +94,8 @@ namespace Tecs { return ss.str(); } - void SaveToCSV(std::ostream &out) { + void SaveToCSV(const std::string &filePath) { + std::ofstream out(filePath); out << "Transaction Event,Transaction Thread Id,Transaction TimeNs"; out << ",Metadata Event,Metadata Thread Id,Metadata TimeNs"; if (componentEvents.size() != componentNames.size()) { @@ -137,6 +142,7 @@ namespace Tecs { } out << std::endl; } + out.close(); } }; @@ -165,7 +171,7 @@ namespace Tecs { inline nonstd::span StopTrace() { if (!traceEnabled) throw std::runtime_error("No trace has been started"); traceEnabled = false; - return nonstd::span(events.data(), std::min(events.size(), (size_t)nextEventIndex.load())); + return nonstd::span(events.data(), std::min((uint32_t)events.size(), nextEventIndex.load())); } private: diff --git a/inc/Tecs_transaction.hh b/inc/Tecs_transaction.hh index f1ae3cf..93681da 100644 --- a/inc/Tecs_transaction.hh +++ b/inc/Tecs_transaction.hh @@ -9,9 +9,11 @@ #ifdef TECS_ENABLE_TRACY #include + #include #include #endif +#include #include #include #include @@ -26,10 +28,10 @@ namespace Tecs { #endif // Used for detecting nested transactions - extern thread_local std::array activeTransactions; + extern thread_local std::array activeTransactions; extern thread_local size_t activeTransactionsCount; - extern std::atomic_size_t nextEcsId; - extern std::atomic_size_t nextTransactionId; + extern std::atomic_uint64_t nextEcsId; + extern std::atomic_uint64_t nextTransactionId; #endif /** @@ -40,43 +42,45 @@ namespace Tecs { * Once a Transaction is deconstructed, all Locks referencing its permissions become invalid. */ template typename ECSType, typename... AllComponentTypes> - class BaseTransaction { + class Transaction> { public: - BaseTransaction(ECSType &instance) : instance(instance) { -#ifndef TECS_HEADER_ONLY - transactionId = ++nextTransactionId; - for (size_t i = 0; i < activeTransactionsCount; i++) { - if (activeTransactions[i] == instance.ecsId) - throw std::runtime_error("Nested transactions are not allowed"); - } - if (activeTransactionsCount == activeTransactions.size()) { - throw std::runtime_error("A single thread can't create more than " - "TECS_MAX_ACTIVE_TRANSACTIONS_PER_THREAD simultaneous transactions"); - } - activeTransactions[activeTransactionsCount++] = instance.ecsId; -#endif - } - // Delete copy constructor - BaseTransaction(const BaseTransaction &) = delete; + using PermissionBitset = std::bitset<1 + sizeof...(AllComponentTypes)>; - virtual ~BaseTransaction() { + private: + using ECS = ECSType; + using EntityMetadata = typename ECS::EntityMetadata; + + ECS &instance; #ifndef TECS_HEADER_ONLY - auto start = activeTransactions.begin(); - activeTransactionsCount = std::remove(start, start + activeTransactionsCount, instance.ecsId) - start; + uint64_t transactionId; #endif + + const PermissionBitset readPermissions; + const PermissionBitset writePermissions; + PermissionBitset writeAccessedFlags; + + public: + template + inline bool IsReadAllowed() const { + return readPermissions[1 + instance.template GetComponentIndex()]; } - protected: - ECSType &instance; -#ifndef TECS_HEADER_ONLY - size_t transactionId; -#endif + template + inline bool IsWriteAllowed() const { + return writePermissions[1 + instance.template GetComponentIndex()]; + } - std::bitset<1 + sizeof...(AllComponentTypes)> writeAccessedFlags; + inline bool IsAddRemoveAllowed() const { + return writePermissions[0]; + } template inline void SetAccessFlag() { - writeAccessedFlags[1 + instance.template GetComponentIndex()] = true; + if constexpr (std::is_same()) { + writeAccessedFlags[0] = true; + } else { + writeAccessedFlags[1 + instance.template GetComponentIndex()] = true; + } } template @@ -85,75 +89,66 @@ namespace Tecs { instance.template Storage().AccessEntity(index); } - template - friend class Lock; - friend struct Entity; - }; - - template - class Transaction, Permissions...> : public BaseTransaction { - private: - using LockType = Lock, Permissions...>; - using EntityMetadata = typename ECS::EntityMetadata; - using FlatPermissions = typename FlattenPermissions::type; +#ifndef TECS_HEADER_ONLY + inline uint64_t GetTransactionId() const { + return transactionId; + } +#endif + Transaction(ECS &instance, const PermissionBitset &readPermissions, const PermissionBitset &writePermissions) + : instance(instance), readPermissions(readPermissions | writePermissions), + writePermissions(writePermissions) { #ifdef TECS_ENABLE_TRACY - static inline const auto tracyCtx = []() -> const tracy::SourceLocationData * { - static const tracy::SourceLocationData srcloc{"TecsTransaction", - FlatPermissions::Name(), - __FILE__, - __LINE__, - 0}; - return &srcloc; - }; - #if defined(TRACY_HAS_CALLSTACK) && defined(TRACY_CALLSTACK) - tracy::ScopedZone tracyZone{tracyCtx(), TRACY_CALLSTACK, true}; - #else - tracy::ScopedZone tracyZone{tracyCtx(), true}; - #endif + ZoneNamedN(tracyScope, "StartTransaction", true); #endif - - public: - inline Transaction(ECS &instance) : BaseTransaction(instance) { #ifdef TECS_ENABLE_PERFORMANCE_TRACING - TECS_EXTERNAL_TRACE_TRANSACTION_STARTING(FlatPermissions::Name()); instance.transactionTrace.Trace(TraceEvent::Type::TransactionStart); #endif -#ifdef TECS_ENABLE_TRACY - ZoneNamedN(tracyScope, "StartTransaction", true); + +#ifndef TECS_HEADER_ONLY + transactionId = ++nextTransactionId; + for (size_t i = 0; i < activeTransactionsCount; i++) { + if (activeTransactions[i] == instance.ecsId) + throw std::runtime_error("Nested transactions are not allowed"); + } + if (activeTransactionsCount == activeTransactions.size()) { + throw std::runtime_error("A single thread can't create more than " + "TECS_MAX_ACTIVE_TRANSACTIONS_PER_THREAD simultaneous transactions"); + } + activeTransactions[activeTransactionsCount++] = instance.ecsId; #endif - std::bitset<1 + sizeof...(AllComponentTypes)> acquired; + PermissionBitset acquired; // Templated lambda functions for Lock/Unlock so they can be looped over at runtime. std::array, acquired.size()> lockFuncs = { - [&instance](bool block) { - if (is_add_remove_allowed()) { + [&](bool block) { + if (IsAddRemoveAllowed()) { return instance.metadata.WriteLock(block); } else { return instance.metadata.ReadLock(block); } }, - [&instance](bool block) { - if (is_write_allowed()) { + [&](bool block) { + if (IsWriteAllowed()) { return instance.template Storage().WriteLock(block); - } else if (is_read_allowed()) { + } else if (IsReadAllowed()) { return instance.template Storage().ReadLock(block); } // This component type isn't part of the lock, skip. return true; }...}; std::array, acquired.size()> unlockFuncs = { - [&instance]() { - if (is_add_remove_allowed()) { + [&]() { + if (IsAddRemoveAllowed()) { return instance.metadata.WriteUnlock(); } else { return instance.metadata.ReadUnlock(); } }, - [&instance]() { - if (is_write_allowed()) { + [&]() { + if (IsWriteAllowed()) { instance.template Storage().WriteUnlock(); - } else if (is_read_allowed()) { + } else if (IsReadAllowed()) { instance.template Storage().ReadUnlock(); } // This component type isn't part of the lock, skip. @@ -181,53 +176,44 @@ namespace Tecs { } } - if constexpr (is_add_remove_allowed()) { + if (IsAddRemoveAllowed()) { // Init observer event queues - this->instance.entityAddEvents.Init(); - this->instance.entityRemoveEvents.Init(); + instance.entityAddEvents.Init(); + instance.entityRemoveEvents.Init(); ( // For each AllComponentTypes, unlock any Noop Writes or Read locks early [&] { - auto &storage = this->instance.template Storage(); + auto &storage = instance.template Storage(); storage.componentAddEvents.Init(); storage.componentRemoveEvents.Init(); storage.componentModifyEvents.Init(); }(), ...); } - -#ifdef TECS_ENABLE_PERFORMANCE_TRACING - TECS_EXTERNAL_TRACE_TRANSACTION_STARTED(FlatPermissions::Name()); -#endif } + // Delete copy constructor + Transaction(const Transaction &) = delete; - inline ~Transaction() { -#ifdef TECS_ENABLE_PERFORMANCE_TRACING - TECS_EXTERNAL_TRACE_TRANSACTION_ENDING(FlatPermissions::Name()); -#endif + ~Transaction() { #ifdef TECS_ENABLE_TRACY ZoneNamedN(tracyTxScope, "EndTransaction", true); #endif - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) PreCommitAddRemoveMetadata(); - } + if (IsAddRemoveAllowed() && writeAccessedFlags[0]) PreCommitAddRemoveMetadata(); ( // For each AllComponentTypes, unlock any Noop Writes or Read locks early [&] { - if constexpr (is_write_allowed()) { - if (!this->instance.template BitsetHas(this->writeAccessedFlags)) { - this->instance.template Storage().WriteUnlock(); + if (IsWriteAllowed()) { + if (!instance.template BitsetHas(writeAccessedFlags)) { + instance.template Storage().WriteUnlock(); } - } else if constexpr (is_read_allowed()) { - this->instance.template Storage().ReadUnlock(); + } else if (IsReadAllowed()) { + instance.template Storage().ReadUnlock(); } }(), ...); ( // For each AllComponentTypes, run pre-commit event handlers [&] { - if constexpr (is_write_allowed()) { - PreCommitComponent(); - } + if (IsWriteAllowed()) PreCommitComponent(); }(), ...); @@ -235,15 +221,14 @@ namespace Tecs { #if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) ZoneNamedN(tracyCommitScope1, "CommitLock", true); #endif - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) this->instance.metadata.CommitLock(); + if (IsAddRemoveAllowed() && writeAccessedFlags[0]) { + instance.metadata.CommitLock(); } ( // For each AllComponentTypes [&] { - if constexpr (is_write_allowed()) { - if (this->instance.template BitsetHas(this->writeAccessedFlags)) { - this->instance.template Storage().CommitLock(); - } + if (IsWriteAllowed() && + instance.template BitsetHas(writeAccessedFlags)) { + instance.template Storage().CommitLock(); } }(), ...); @@ -254,55 +239,48 @@ namespace Tecs { #endif // Commit observers - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) { + if (IsAddRemoveAllowed() && writeAccessedFlags[0]) { #if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) - ZoneNamedN(tracyCommitScope3, "CommitEntityEvent", true); + ZoneNamedN(tracyCommitScope3, "CommitEntityEvent", true); #endif - this->instance.entityAddEvents.Commit(); - this->instance.entityRemoveEvents.Commit(); - } + instance.entityAddEvents.Commit(); + instance.entityRemoveEvents.Commit(); } ( // For each AllComponentTypes [&] { - if constexpr (is_write_allowed()) { - if (this->instance.template BitsetHas(this->writeAccessedFlags)) { + if (IsWriteAllowed() && + instance.template BitsetHas(writeAccessedFlags)) { #if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) - ZoneNamedN(tracyCommitScope3, "CommitComponentEvent", true); - ZoneTextV(tracyCommitScope3, - typeid(AllComponentTypes).name(), - std::strlen(typeid(AllComponentTypes).name())); + ZoneNamedN(tracyCommitScope3, "CommitComponentEvent", true); + ZoneTextV(tracyCommitScope3, + typeid(AllComponentTypes).name(), + std::strlen(typeid(AllComponentTypes).name())); #endif - auto &storage = this->instance.template Storage(); - if constexpr (is_add_remove_allowed()) { - storage.componentAddEvents.Commit(); - storage.componentRemoveEvents.Commit(); - } - storage.componentModifyEvents.Commit(); + auto &storage = instance.template Storage(); + if (IsAddRemoveAllowed()) { + storage.componentAddEvents.Commit(); + storage.componentRemoveEvents.Commit(); } + storage.componentModifyEvents.Commit(); } }(), ...); - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) { - this->instance.metadata.readComponents.swap(this->instance.metadata.writeComponents); - this->instance.metadata.readValidEntities.swap(this->instance.metadata.writeValidEntities); - this->instance.globalReadMetadata = this->instance.globalWriteMetadata; - this->instance.metadata.CommitUnlock(); - } + if (IsAddRemoveAllowed() && writeAccessedFlags[0]) { + instance.metadata.readComponents.swap(instance.metadata.writeComponents); + instance.metadata.readValidEntities.swap(instance.metadata.writeValidEntities); + instance.globalReadMetadata = instance.globalWriteMetadata; + instance.metadata.CommitUnlock(); } ( // For each AllComponentTypes [&] { - if constexpr (is_write_allowed()) { + if (IsWriteAllowed()) { // Skip if no write accesses were made - if (!this->instance.template BitsetHas(this->writeAccessedFlags)) return; - auto &storage = this->instance.template Storage(); + if (!instance.template BitsetHas(writeAccessedFlags)) return; + auto &storage = instance.template Storage(); storage.readComponents.swap(storage.writeComponents); - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) { - storage.readValidEntities.swap(storage.writeValidEntities); - } + if (IsAddRemoveAllowed() && writeAccessedFlags[0]) { + storage.readValidEntities.swap(storage.writeValidEntities); } storage.CommitUnlock(); } @@ -312,7 +290,7 @@ namespace Tecs { ( // For each AllComponentTypes, reset the write storage to match read. [&] { - if constexpr (is_write_allowed()) { + if (IsWriteAllowed()) { #if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) ZoneNamedN(tracyCommitScope3, "CopyReadComponent", true); ZoneTextV(tracyCommitScope3, @@ -320,12 +298,12 @@ namespace Tecs { std::strlen(typeid(AllComponentTypes).name())); #endif // Skip if no write accesses were made - if (!this->instance.template BitsetHas(this->writeAccessedFlags)) return; - auto &storage = this->instance.template Storage(); + if (!instance.template BitsetHas(writeAccessedFlags)) return; + auto &storage = instance.template Storage(); if constexpr (is_global_component()) { storage.writeComponents = storage.readComponents; - } else if (is_add_remove_allowed() && this->writeAccessedFlags[0]) { + } else if (IsAddRemoveAllowed() && writeAccessedFlags[0]) { if (storage.writeComponents.size() != storage.readComponents.size()) { storage.writeComponents.resize(storage.readComponents.size()); } @@ -343,19 +321,22 @@ namespace Tecs { } }(), ...); - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) { - this->instance.metadata.writeComponents = this->instance.metadata.readComponents; - this->instance.metadata.writeValidEntities = this->instance.metadata.readValidEntities; + if (IsAddRemoveAllowed()) { + if (writeAccessedFlags[0]) { + instance.metadata.writeComponents = instance.metadata.readComponents; + instance.metadata.writeValidEntities = instance.metadata.readValidEntities; } - this->instance.metadata.WriteUnlock(); + instance.metadata.WriteUnlock(); } else { - this->instance.metadata.ReadUnlock(); + instance.metadata.ReadUnlock(); } +#ifndef TECS_HEADER_ONLY + auto start = activeTransactions.begin(); + activeTransactionsCount = std::remove(start, start + activeTransactionsCount, instance.ecsId) - start; +#endif #ifdef TECS_ENABLE_PERFORMANCE_TRACING - TECS_EXTERNAL_TRACE_TRANSACTION_ENDED(FlatPermissions::Name()); - this->instance.transactionTrace.Trace(TraceEvent::Type::TransactionEnd); + instance.transactionTrace.Trace(TraceEvent::Type::TransactionEnd); #endif } @@ -363,37 +344,37 @@ namespace Tecs { inline static const EntityMetadata emptyMetadata = {}; inline void PreCommitAddRemoveMetadata() const { +#if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) + ZoneNamedN(tracyScope, "PreCommitAddRemoveMetadata", true); +#endif // Rebuild writeValidEntities, validEntityIndexes, and freeEntities with the new entity set. - this->instance.metadata.writeValidEntities.clear(); - this->instance.freeEntities.clear(); + instance.metadata.writeValidEntities.clear(); + instance.freeEntities.clear(); - const auto &writeMetadataList = this->instance.metadata.writeComponents; + const auto &writeMetadataList = instance.metadata.writeComponents; for (TECS_ENTITY_INDEX_TYPE index = 0; index < writeMetadataList.size(); index++) { const auto &newMetadata = writeMetadataList[index]; - const auto &oldMetadata = index >= this->instance.metadata.readComponents.size() + const auto &oldMetadata = index >= instance.metadata.readComponents.size() ? emptyMetadata - : this->instance.metadata.readComponents[index]; + : instance.metadata.readComponents[index]; // If this index exists, add it to the valid entity lists. if (newMetadata[0]) { - this->instance.metadata.validEntityIndexes[index] = - this->instance.metadata.writeValidEntities.size(); - this->instance.metadata.writeValidEntities.emplace_back(index, newMetadata.generation); + instance.metadata.validEntityIndexes[index] = instance.metadata.writeValidEntities.size(); + instance.metadata.writeValidEntities.emplace_back(index, newMetadata.generation); } else { - this->instance.freeEntities.emplace_back(index, + instance.freeEntities.emplace_back(index, newMetadata.generation + 1, - (TECS_ENTITY_ECS_IDENTIFIER_TYPE)this->instance.ecsId); + (TECS_ENTITY_ECS_IDENTIFIER_TYPE)instance.ecsId); } // Compare new and old metadata to notify observers if (newMetadata[0] != oldMetadata[0] || newMetadata.generation != oldMetadata.generation) { - if (oldMetadata[0]) { - this->instance.entityRemoveEvents.AddEvent(EventType::REMOVED, - Entity(index, oldMetadata.generation)); + if (oldMetadata[0] && !instance.entityRemoveEvents.IsEmpty()) { + instance.entityRemoveEvents.AddEvent(EventType::REMOVED, Entity(index, oldMetadata.generation)); } - if (newMetadata[0]) { - this->instance.entityAddEvents.AddEvent(EventType::ADDED, - Entity(index, newMetadata.generation)); + if (newMetadata[0] && !instance.entityAddEvents.IsEmpty()) { + instance.entityAddEvents.AddEvent(EventType::ADDED, Entity(index, newMetadata.generation)); } } } @@ -409,27 +390,29 @@ namespace Tecs { template inline void PreCommitComponent() const { #if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) - ZoneNamedN(tracyPreCommitScope, "PreCommitComponent", true); + ZoneNamedN(tracyScope, "PreCommitComponent", true); + ZoneTextV(tracyScope, typeid(U).name(), std::strlen(typeid(U).name())); #endif - auto &storage = this->instance.template Storage(); + auto &storage = instance.template Storage(); if constexpr (is_global_component()) { - if (this->writeAccessedFlags[0]) { - const auto &oldMetadata = this->instance.globalReadMetadata; - const auto &newMetadata = this->instance.globalWriteMetadata; - if (this->instance.template BitsetHas(newMetadata)) { - if (!this->instance.template BitsetHas(oldMetadata)) { + if (writeAccessedFlags[0]) { + const auto &oldMetadata = instance.globalReadMetadata; + const auto &newMetadata = instance.globalWriteMetadata; + if (instance.template BitsetHas(newMetadata)) { + if (!instance.template BitsetHas(oldMetadata) && !storage.componentAddEvents.IsEmpty()) { storage.componentAddEvents.AddEvent(EventType::ADDED, Entity(), storage.writeComponents[0]); } - } else if (this->instance.template BitsetHas(oldMetadata)) { + } else if (instance.template BitsetHas(oldMetadata) && + !storage.componentRemoveEvents.IsEmpty()) { storage.componentRemoveEvents.AddEvent(EventType::REMOVED, Entity(), storage.readComponents[0]); } } - if (this->instance.template BitsetHas(this->writeAccessedFlags)) { - const auto &newMetadata = is_add_remove_allowed() ? this->instance.globalWriteMetadata - : this->instance.globalReadMetadata; - const auto &oldMetadata = this->instance.globalReadMetadata; - if (this->instance.template BitsetHas(newMetadata) && - this->instance.template BitsetHas(oldMetadata)) { + if (instance.template BitsetHas(writeAccessedFlags) && !storage.componentModifyEvents.IsEmpty()) { + const auto &newMetadata = + IsAddRemoveAllowed() ? instance.globalWriteMetadata : instance.globalReadMetadata; + // const auto &oldMetadata = instance.globalReadMetadata; + if (instance.template BitsetHas(newMetadata)) { + // && instance.template BitsetHas(oldMetadata)) { if constexpr (is_equals_comparable()) { if (storage.writeComponents[0] != storage.readComponents[0]) { storage.componentModifyEvents.AddEvent(); @@ -440,59 +423,167 @@ namespace Tecs { } } } else { - if (this->writeAccessedFlags[0]) { + if (writeAccessedFlags[0]) { +#if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) + ZoneNamedN(tracyScope2, "RebuildValid", true); + ZoneTextV(tracyScope2, typeid(U).name(), std::strlen(typeid(U).name())); +#endif // Rebuild writeValidEntities and validEntityIndexes with the new entity set. storage.writeValidEntities.clear(); - const auto &writeMetadataList = this->instance.metadata.writeComponents; - for (TECS_ENTITY_INDEX_TYPE index = 0; index < writeMetadataList.size(); index++) { - const auto &newMetadata = writeMetadataList[index]; - const auto &oldMetadata = index >= this->instance.metadata.readComponents.size() - ? emptyMetadata - : this->instance.metadata.readComponents[index]; - - // If this index exists, add it to the valid entity lists. - if (newMetadata[0] && this->instance.template BitsetHas(newMetadata)) { + const auto &writeMetadataList = instance.metadata.writeComponents; + if (storage.componentRemoveEvents.IsEmpty() && storage.componentAddEvents.IsEmpty()) { + // Fast version skipping event checks + for (TECS_ENTITY_INDEX_TYPE index = 0; index < writeMetadataList.size(); index++) { + const auto &newMetadata = writeMetadataList[index]; - storage.validEntityIndexes[index] = storage.writeValidEntities.size(); - storage.writeValidEntities.emplace_back(index, newMetadata.generation); + if (newMetadata[0] && instance.template BitsetHas(newMetadata)) { + // If this index exists, add it to the valid entity lists. + storage.validEntityIndexes[index] = storage.writeValidEntities.size(); + storage.writeValidEntities.emplace_back(index, newMetadata.generation); + } } + } else { + // Slightly slower version with event checks + for (TECS_ENTITY_INDEX_TYPE index = 0; index < writeMetadataList.size(); index++) { + const auto &newMetadata = writeMetadataList[index]; + bool newExists = newMetadata[0] && instance.template BitsetHas(newMetadata); - // Compare new and old metadata to notify observers - bool newExists = this->instance.template BitsetHas(newMetadata); - bool oldExists = this->instance.template BitsetHas(oldMetadata); - if (newExists != oldExists || newMetadata.generation != oldMetadata.generation) { - if (oldExists) { - storage.componentRemoveEvents.AddEvent(EventType::REMOVED, - Entity(index, oldMetadata.generation), - storage.readComponents[index]); - } if (newExists) { - storage.componentAddEvents.AddEvent(EventType::ADDED, - Entity(index, newMetadata.generation), - storage.writeComponents[index]); + // If this index exists, add it to the valid entity lists. + storage.validEntityIndexes[index] = storage.writeValidEntities.size(); + storage.writeValidEntities.emplace_back(index, newMetadata.generation); + } + + // Compare new and old metadata to notify observers + const auto &oldMetadata = index >= instance.metadata.readComponents.size() + ? emptyMetadata + : instance.metadata.readComponents[index]; + bool oldExists = oldMetadata[0] && instance.template BitsetHas(oldMetadata); + if (newExists != oldExists || newMetadata.generation != oldMetadata.generation) { + if (oldExists) { + storage.componentRemoveEvents.AddEvent(EventType::REMOVED, + Entity(index, oldMetadata.generation), + storage.readComponents[index]); + } + if (newExists) { + storage.componentAddEvents.AddEvent(EventType::ADDED, + Entity(index, newMetadata.generation), + storage.writeComponents[index]); + } } } } } - if (this->instance.template BitsetHas(this->writeAccessedFlags)) { + if (instance.template BitsetHas(writeAccessedFlags) && !storage.componentModifyEvents.IsEmpty()) { for (auto &index : storage.writeAccessedEntities) { - const auto &newMetadata = is_add_remove_allowed() - ? this->instance.metadata.writeComponents[index] - : this->instance.metadata.readComponents[index]; - const auto &oldMetadata = index >= this->instance.metadata.readComponents.size() + const auto &newMetadata = IsAddRemoveAllowed() ? instance.metadata.writeComponents[index] + : instance.metadata.readComponents[index]; + const auto &oldMetadata = index >= instance.metadata.readComponents.size() ? emptyMetadata - : this->instance.metadata.readComponents[index]; - if (this->instance.template BitsetHas(newMetadata) && - this->instance.template BitsetHas(oldMetadata)) { - if constexpr (is_equals_comparable()) { + : instance.metadata.readComponents[index]; + if constexpr (is_equals_comparable()) { + // If this is the same entity, check the equality operator + if (instance.template BitsetHas(newMetadata) && + instance.template BitsetHas(oldMetadata) && + newMetadata.generation == oldMetadata.generation) { if (storage.writeComponents[index] == storage.readComponents[index]) continue; } + } + // If the generation was changed or the entity was removed, consider the old generation modified + if (instance.template BitsetHas(oldMetadata) && + (!instance.template BitsetHas(newMetadata) || + newMetadata.generation != oldMetadata.generation)) { + storage.componentModifyEvents.AddEvent(Entity(index, oldMetadata.generation)); + } + if (instance.template BitsetHas(newMetadata)) { storage.componentModifyEvents.AddEvent(Entity(index, newMetadata.generation)); } } } } } + +#ifdef TECS_ENABLE_TRACY + const std::string permissionsStr = [&]() { + ZoneScopedN("PermissionsStrGen"); + std::stringstream out, write, read; + out << "Permissions<"; + if (IsAddRemoveAllowed()) { + out << "AddRemove"; + } else if (writePermissions.all()) { + out << "WriteAll"; + } else { + bool firstOut = true, firstRead = true, firstWrite = true; + for (size_t i = 1; i <= sizeof...(AllComponentTypes); i++) { + if (writePermissions[i]) { + if (firstWrite) { + firstWrite = false; + } else { + write << ", "; + } + write << ECS::GetComponentName(i - 1); + } + } + if (!firstWrite) { + if (firstOut) { + firstOut = false; + } else { + out << ", "; + } + out << "Write<" << write.str() << ">"; + } + if (readPermissions.all()) { + if (firstOut) { + firstOut = false; + } else { + out << ", "; + } + out << "ReadAll"; + } else { + for (size_t i = 1; i <= sizeof...(AllComponentTypes); i++) { + if (readPermissions[i] && !writePermissions[i]) { + if (firstRead) { + firstRead = false; + } else { + read << ", "; + } + read << ECS::GetComponentName(i - 1); + } + } + if (!firstRead) { + if (firstOut) { + firstOut = false; + } else { + out << ", "; + } + out << "Read<" << read.str() << ">"; + } + } + } + out << ">"; + return out.str(); + }(); + #if defined(TRACY_HAS_CALLSTACK) && defined(TRACY_CALLSTACK) + tracy::ScopedZone tracyZone{__LINE__, + __FILE__, + strlen(__FILE__), + permissionsStr.c_str(), + permissionsStr.size(), + "TecsTransaction", + strlen("TecsTransaction"), + TRACY_CALLSTACK, + true}; + #else + tracy::ScopedZone tracyZone{__LINE__, + __FILE__, + strlen(__FILE__), + permissionsStr.c_str(), + permissionsStr.size(), + "TecsTransaction", + strlen("TecsTransaction"), + true}; + #endif +#endif }; } // namespace Tecs diff --git a/inc/c_abi/Tecs.h b/inc/c_abi/Tecs.h new file mode 100644 index 0000000..b8a9efa --- /dev/null +++ b/inc/c_abi/Tecs.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Tecs_export.h" +#include "Tecs_lock.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void tecs_ecs_t; + +TECS_EXPORT tecs_ecs_t *Tecs_make_ecs_instance(); +TECS_EXPORT void Tecs_release_ecs_instance(tecs_ecs_t *ecsPtr); + +TECS_EXPORT tecs_lock_t *Tecs_ecs_start_transaction(tecs_ecs_t *ecsPtr, uint64_t readPermissions, + uint64_t writePermissions); +TECS_EXPORT tecs_lock_t *Tecs_ecs_start_transaction_bitstr(tecs_ecs_t *ecsPtr, const char *readPermissions, + const char *writePermissions); + +TECS_EXPORT uint64_t Tecs_ecs_get_instance_id(tecs_ecs_t *ecsPtr); +TECS_EXPORT uint64_t Tecs_ecs_get_next_transaction_id(tecs_ecs_t *ecsPtr); +TECS_EXPORT uint32_t Tecs_ecs_get_component_count(); +TECS_EXPORT size_t Tecs_ecs_get_component_size(uint32_t componentIndex); +TECS_EXPORT size_t Tecs_ecs_get_component_name(uint32_t componentIndex, size_t bufferSize, char *output); +TECS_EXPORT size_t Tecs_ecs_get_bytes_per_entity(); + +TECS_EXPORT void Tecs_lock_release(tecs_lock_t *dynLockPtr); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/inc/c_abi/Tecs.hh b/inc/c_abi/Tecs.hh new file mode 100644 index 0000000..3dc0cd9 --- /dev/null +++ b/inc/c_abi/Tecs.hh @@ -0,0 +1,183 @@ +#pragma once + +#include "Tecs.h" +#include "Tecs_lock.hh" +#ifdef TECS_ENABLE_PERFORMANCE_TRACING + #include "Tecs_tracing.hh" +#endif + +#include + +// Declare this once in the root namespace +#define TECS_IMPLEMENT_C_ABI thread_local size_t Tecs::abi::cacheInvalidationCounter; + +namespace Tecs::abi { + /** + * An ECS "world" is created by instantiating this class. Component types must be known at compile-time and are + * passed in as template arguments. + * + * All operations are done through Transactions with Read, Write, or AddRemove permissions. + * These transactions are thread-safe, and can be run simultaniously from multiple threads with no additional + * external synchronization. + * + * It is recommended to instantiate an ECS in a static place in memory so it can be easily accessed from multiple + * threads. + */ + template + class ECS { + private: + tecs_ecs_t *base = nullptr; + bool shouldReleaseBase = false; + + public: + inline ECS(tecs_ecs_t *base) : base(base) { + auto count = GetComponentCount(); + auto baseCount = Tecs_ecs_get_component_count(); + if (count != baseCount) { + throw std::runtime_error("Component count missmatch: count " + std::to_string(count) + " != base " + + std::to_string(baseCount)); + } + ( + [&] { + auto size = sizeof(Tn); + auto baseSize = Tecs_ecs_get_component_size(GetComponentIndex()); + if (size != baseSize) { + throw std::runtime_error("Component " + std::string(typeid(Tn).name()) + + " size missmatch: size " + std::to_string(size) + " != base " + + std::to_string(baseSize)); + } + }(), + ...); + } + + inline ECS() : ECS(Tecs_make_ecs_instance()) { + shouldReleaseBase = true; + } + + inline ~ECS() { + if (shouldReleaseBase) Tecs_release_ecs_instance(base); + } + + /** + * Start a new transaction with a specific set of permissions, and return a Lock object. + * + * Permissions can be any combination of the following: + * Tecs::Read - Allow read-only access to a list of Component types + * Tecs::ReadAll - Allow read-only access to all existing Components + * Tecs::Write - Allow write access to a list of Component types (existing Components only) + * Tecs::WriteAll - Allow write access to all existing Components + * Tecs::AddRemove - Allow the creation and deletion of new Entities and Components + * + * It is recommended to start transactions with the minimum required permissions to prevent unnecessary thread + * synchronization. Only a single transaction should be active in a single thread at a time; nesting + * transactions is undefined behavior and may result in deadlocks. + * + * All data access must be done through the returned Lock object, or by passing the lock to an Entity function. + */ + template + inline Lock, Permissions...> StartTransaction() { + using LockType = Lock, Permissions...>; + + std::bitset<1 + sizeof...(Tn)> readPermissions; + std::bitset<1 + sizeof...(Tn)> writePermissions; + readPermissions[0] = true; + writePermissions[0] = is_add_remove_allowed(); + // clang-format off + (( + readPermissions[1 + GetComponentIndex()] = is_read_allowed(), + writePermissions[1 + GetComponentIndex()] = is_write_allowed() + ), ...); + // clang-format on + tecs_lock_t *l = nullptr; + if constexpr (sizeof...(Tn) <= std::numeric_limits::digits) { + l = Tecs_ecs_start_transaction(base, readPermissions.to_ullong(), writePermissions.to_ullong()); + } else { + l = Tecs_ecs_start_transaction_bitstr(base, + readPermissions.to_string().c_str(), + writePermissions.to_string().c_str()); + } + std::shared_ptr lockPtr(l, [](auto *ptr) { + Tecs_lock_release(ptr); + }); + return LockType(lockPtr); + } + +#ifdef TECS_ENABLE_PERFORMANCE_TRACING + inline void StartTrace() { + Tecs_ecs_start_perf_trace(base); + } + + inline PerformanceTrace StopTrace() { + tecs_perf_trace_t *trace = Tecs_ecs_stop_perf_trace(base); + return PerformanceTrace{std::shared_ptr(trace, [](auto *ptr) { + Tecs_ecs_perf_trace_release(ptr); + })}; + } +#endif + + inline TECS_ENTITY_ECS_IDENTIFIER_TYPE GetInstanceId() const { + return (TECS_ENTITY_ECS_IDENTIFIER_TYPE)Tecs_ecs_get_instance_id(base); + } + + inline uint64_t GetNextTransactionId() const { + return Tecs_ecs_get_next_transaction_id(base); + } + + /** + * Returns the index of a Component type for use in a bitset. + */ + template + inline static constexpr uint32_t GetComponentIndex() { + return GetComponentIndex<0, U>(); + } + + /** + * Returns the number of Component types registered in this ECS instance. + */ + inline static constexpr uint32_t GetComponentCount() { + return sizeof...(Tn); + } + + /** + * Returns the registered name of the Nth Component type, or a default of "ComponentN" if none is set. + */ + inline std::string GetComponentName(uint32_t componentIndex) { + size_t size = Tecs_ecs_get_component_name(componentIndex, 0, nullptr); + std::string str(size, '\0'); + Tecs_ecs_get_component_name(componentIndex, size, str.data()); + return str; + } + + /** + * Returns the registered name of a Component type, or a default of "ComponentN" if none is set. + */ + template + inline std::string GetComponentName() { + return GetComponentName(GetComponentIndex()); + } + + /** + * Returns true if the Component type is part of this ECS. + */ + template + inline static constexpr bool IsComponent() { + return contains(); + } + + inline size_t GetBytesPerEntity() { + return Tecs_ecs_get_bytes_per_entity(); + } + + private: + template + inline static constexpr uint32_t GetComponentIndex() { + static_assert(I < sizeof...(Tn), "Component does not exist"); + + if constexpr (std::is_same>::type>::value) { + return I; + } else { + return GetComponentIndex(); + } + } + }; +} // namespace Tecs::abi diff --git a/inc/c_abi/Tecs_entity.h b/inc/c_abi/Tecs_entity.h new file mode 100644 index 0000000..8fbb326 --- /dev/null +++ b/inc/c_abi/Tecs_entity.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Tecs_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +static_assert(sizeof(bool) == 1, "Unexpected bool size"); + +typedef void tecs_lock_t; +typedef uint64_t tecs_entity_t; + +TECS_EXPORT const void *Tecs_get_entity_storage(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT const void *Tecs_get_previous_entity_storage(tecs_lock_t *dynLockPtr, uint32_t componentIndex); + +TECS_EXPORT bool Tecs_entity_exists(tecs_lock_t *dynLockPtr, tecs_entity_t entity); +TECS_EXPORT bool Tecs_entity_existed(tecs_lock_t *dynLockPtr, tecs_entity_t entity); +TECS_EXPORT bool Tecs_entity_has(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex); +TECS_EXPORT bool Tecs_entity_had(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex); +TECS_EXPORT bool Tecs_entity_has_bitset(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint64_t componentBits); +TECS_EXPORT bool Tecs_entity_had_bitset(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint64_t componentBits); +TECS_EXPORT const void *Tecs_entity_const_get(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex); +TECS_EXPORT void *Tecs_entity_get(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex); +TECS_EXPORT const void *Tecs_entity_get_previous(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex); +TECS_EXPORT void *Tecs_entity_set(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex, + const void *value); +TECS_EXPORT void Tecs_entity_unset(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex); +TECS_EXPORT void Tecs_entity_destroy(tecs_lock_t *dynLockPtr, tecs_entity_t entity); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/inc/c_abi/Tecs_entity.hh b/inc/c_abi/Tecs_entity.hh new file mode 100644 index 0000000..b49750b --- /dev/null +++ b/inc/c_abi/Tecs_entity.hh @@ -0,0 +1,275 @@ +#pragma once + +#include "../Tecs_permissions.hh" +#include "Tecs_entity.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef TECS_ENTITY_INDEX_TYPE + #define TECS_ENTITY_INDEX_TYPE uint32_t +#endif + +#ifndef TECS_ENTITY_GENERATION_TYPE + #define TECS_ENTITY_GENERATION_TYPE uint32_t +#endif + +#ifndef TECS_ENTITY_ECS_IDENTIFIER_TYPE + #define TECS_ENTITY_ECS_IDENTIFIER_TYPE uint8_t +#endif + +static_assert(sizeof(TECS_ENTITY_GENERATION_TYPE) > sizeof(TECS_ENTITY_ECS_IDENTIFIER_TYPE), + "TECS_ENTITY_ECS_IDENTIFIER_TYPE must fit within TECS_ENTITY_GENERATION_TYPE"); + +namespace Tecs::abi { + struct Entity; + + extern thread_local size_t cacheInvalidationCounter; +}; // namespace Tecs::abi + +namespace std { + inline string to_string(const Tecs::abi::Entity &ent); +}; + +namespace Tecs::abi { + constexpr TECS_ENTITY_GENERATION_TYPE GenerationWithoutIdentifier(TECS_ENTITY_GENERATION_TYPE generation) { + constexpr size_t generationBits = + (sizeof(TECS_ENTITY_GENERATION_TYPE) - sizeof(TECS_ENTITY_ECS_IDENTIFIER_TYPE)) * 8; + constexpr auto generationMask = ((TECS_ENTITY_GENERATION_TYPE)1 << generationBits) - 1; + return generation & generationMask; + } + + constexpr TECS_ENTITY_GENERATION_TYPE GenerationWithIdentifier(TECS_ENTITY_GENERATION_TYPE generation, + TECS_ENTITY_ECS_IDENTIFIER_TYPE ecsId) { + constexpr size_t generationBits = + (sizeof(TECS_ENTITY_GENERATION_TYPE) - sizeof(TECS_ENTITY_ECS_IDENTIFIER_TYPE)) * 8; + return GenerationWithoutIdentifier(generation) | ((TECS_ENTITY_GENERATION_TYPE)ecsId << generationBits); + } + + constexpr TECS_ENTITY_ECS_IDENTIFIER_TYPE IdentifierFromGeneration(TECS_ENTITY_GENERATION_TYPE generation) { + constexpr size_t generationBits = + (sizeof(TECS_ENTITY_GENERATION_TYPE) - sizeof(TECS_ENTITY_ECS_IDENTIFIER_TYPE)) * 8; + return (TECS_ENTITY_ECS_IDENTIFIER_TYPE)(generation >> generationBits); + } + + struct Entity { + // Workaround for Clang so that std::atomic operations can be inlined as if uint64. See issue: + // https://stackoverflow.com/questions/60445848/clang-doesnt-inline-stdatomicload-for-loading-64-bit-structs + alignas(sizeof(TECS_ENTITY_GENERATION_TYPE) + sizeof(TECS_ENTITY_INDEX_TYPE)) TECS_ENTITY_INDEX_TYPE index; + TECS_ENTITY_GENERATION_TYPE generation; + + inline Entity() : index(0), generation(0) {} + + inline Entity(uint64_t eid) + : index(eid & std::numeric_limits::max()), + generation(eid >> (sizeof(TECS_ENTITY_INDEX_TYPE) * 8)) {} + inline Entity(TECS_ENTITY_INDEX_TYPE index, TECS_ENTITY_GENERATION_TYPE generation) + : index(index), generation(generation) {} + inline Entity(TECS_ENTITY_INDEX_TYPE index, TECS_ENTITY_GENERATION_TYPE generation, + TECS_ENTITY_ECS_IDENTIFIER_TYPE ecsId) + : index(index), generation(GenerationWithIdentifier(generation, ecsId)) {} + + public: + template + inline bool Exists(const LockType &lock) const { + return Tecs_entity_exists(lock.base.get(), (tecs_entity_t)(*this)); + } + + template + inline bool Existed(const LockType &lock) const { + return Tecs_entity_existed(lock.base.get(), (tecs_entity_t)(*this)); + } + + template + inline bool Has(const LockType &lock) const { + static_assert(!contains_global_components(), "Entities cannot have global components"); + + if constexpr (LockType::ECS::GetComponentCount() < std::numeric_limits::digits) { + std::bitset<1 + LockType::ECS::GetComponentCount()> componentBits; + componentBits[0] = true; + ((componentBits[1 + LockType::ECS::template GetComponentIndex()] = true), ...); + return Tecs_entity_has_bitset(lock.base.get(), (tecs_entity_t)(*this), componentBits.to_ullong()); + } else { + return (Tecs_entity_has(lock.base.get(), + (tecs_entity_t)(*this), + LockType::ECS::template GetComponentIndex()) && + ...); + } + } + + template + inline bool Had(const LockType &lock) const { + static_assert(!contains_global_components(), "Entities cannot have global components"); + + if constexpr (LockType::ECS::GetComponentCount() < std::numeric_limits::digits) { + std::bitset<1 + LockType::ECS::GetComponentCount()> componentBits; + componentBits[0] = true; + ((componentBits[1 + LockType::ECS::template GetComponentIndex()] = true), ...); + return Tecs_entity_had_bitset(lock.base.get(), (tecs_entity_t)(*this), componentBits.to_ullong()); + } else { + return (Tecs_entity_had(lock.base.get(), + (tecs_entity_t)(*this), + LockType::ECS::template GetComponentIndex()) && + ...); + } + } + + template, LockType>::value, T, const T>> + inline ReturnType &Get(const LockType &lock) const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + static_assert(is_write_allowed() || std::is_const(), + "Can't get non-const reference of read only Component."); + static_assert(!is_global_component(), "Global components must be accessed through lock.Get()"); + + if (cacheInvalidationCounter != lock.cacheCounter) { + lock.cachedStorage = {}; + lock.cachedPreviousStorage = {}; + lock.cacheCounter = cacheInvalidationCounter; + } + + constexpr uint32_t componentIndex = LockType::ECS::template GetComponentIndex(); + if constexpr (std::is_const()) { + auto *&cachedStorage = std::get(lock.cachedStorage); + if (!cachedStorage) { + cachedStorage = + static_cast(Tecs_get_entity_storage(lock.base.get(), componentIndex)); + } + return cachedStorage[index]; + } else { + return *static_cast(Tecs_entity_get(lock.base.get(), (tecs_entity_t)(*this), componentIndex)); + } + } + + template + inline const T &GetPrevious(const LockType &lock) const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + static_assert(!is_global_component(), + "Global components must be accessed through lock.GetPrevious()"); + + if (cacheInvalidationCounter != lock.cacheCounter) { + lock.cachedStorage = {}; + lock.cachedPreviousStorage = {}; + lock.cacheCounter = cacheInvalidationCounter; + } + auto *&cachedPreviousStorage = std::get(lock.cachedPreviousStorage); + if (!cachedPreviousStorage) { + constexpr uint32_t componentIndex = LockType::ECS::template GetComponentIndex(); + cachedPreviousStorage = + static_cast(Tecs_get_previous_entity_storage(lock.base.get(), componentIndex)); + } + + return cachedPreviousStorage[index]; + } + + template + inline T &Set(const LockType &lock, T &value) const { + static_assert(is_write_allowed(), "Component is not locked for writing."); + static_assert(!is_global_component(), "Global components must be accessed through lock.Set()"); + + constexpr uint32_t componentIndex = LockType::ECS::template GetComponentIndex(); + return *static_cast(Tecs_entity_set(lock.base.get(), (tecs_entity_t)(*this), componentIndex, &value)); + } + + template + inline T &Set(const LockType &lock, Args... args) const { + static_assert(is_write_allowed(), "Component is not locked for writing."); + static_assert(!is_global_component(), "Global components must be accessed through lock.Set()"); + + T temp = T(std::forward(args)...); + + constexpr uint32_t componentIndex = LockType::ECS::template GetComponentIndex(); + return *static_cast(Tecs_entity_set(lock.base.get(), (tecs_entity_t)(*this), componentIndex, &temp)); + } + + template + inline void Unset(const LockType &lock) const { + static_assert(is_add_remove_allowed(), "Components cannot be removed without an AddRemove lock."); + static_assert(!contains_global_components(), + "Global components must be removed through lock.Unset()"); + + (Tecs_entity_unset(lock.base.get(), + (tecs_entity_t)(*this), + LockType::ECS::template GetComponentIndex()), + ...); + } + + template + inline void Destroy(const LockType &lock) const { + static_assert(is_add_remove_allowed(), "Entities cannot be destroyed without an AddRemove lock."); + + Tecs_entity_destroy(lock.base.get(), (tecs_entity_t)(*this)); + } + + template + inline void Destroy(const LockType &lock) { + static_assert(is_add_remove_allowed(), "Entities cannot be destroyed without an AddRemove lock."); + + ((const Entity *)this)->Destroy(lock); + + generation = 0; + index = 0; + } + + inline bool operator==(const Entity &other) const { + return index == other.index && generation == other.generation; + } + + inline bool operator!=(const Entity &other) const { + return index != other.index || generation != other.generation; + } + + inline bool operator<(const Entity &other) const { + return (index == other.index) ? (generation < other.generation) : (index < other.index); + } + + inline bool operator!() const { + return generation == 0; + } + + inline explicit operator bool() const { + return generation != 0; + } + + inline explicit operator uint64_t() const { + return (((uint64_t)generation) << (sizeof(TECS_ENTITY_INDEX_TYPE) * 8)) | (uint64_t)index; + } + }; + + static_assert(sizeof(Entity) <= sizeof(uint64_t), "Tecs::Entity must not exceed 64 bits"); +} // namespace Tecs::abi + +namespace std { + template<> + struct hash { + std::size_t operator()(const Tecs::abi::Entity &e) const { + const auto genBits = sizeof(TECS_ENTITY_GENERATION_TYPE) * 8; + uint64_t value = (uint64_t)e.index << genBits | e.generation; + return hash{}(value); + } + }; + + inline string to_string(const Tecs::abi::Entity &ent) { + if (ent) { + auto ecsId = Tecs::abi::IdentifierFromGeneration(ent.generation); + auto generation = Tecs::abi::GenerationWithoutIdentifier(ent.generation); + if (ecsId == 1) { + // Simplify logging for the common case of 1 ECS instance. + return "Entity(gen " + to_string(generation) + ", index " + to_string(ent.index) + ")"; + } else { + return "Entity(ecs " + to_string(ecsId) + ", gen " + to_string(generation) + ", index " + + to_string(ent.index) + ")"; + } + } else { + return "Entity(invalid)"; + } + } +} // namespace std diff --git a/inc/c_abi/Tecs_entity_view.h b/inc/c_abi/Tecs_entity_view.h new file mode 100644 index 0000000..ad08135 --- /dev/null +++ b/inc/c_abi/Tecs_entity_view.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Tecs_entity.h" +#include "Tecs_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct tecs_entity_view_t { + const void *storage; + uint64_t start_index; + uint64_t end_index; +} tecs_entity_view_t; + +TECS_EXPORT uint64_t Tecs_entity_view_storage_size(const tecs_entity_view_t *view); +TECS_EXPORT const tecs_entity_t *Tecs_entity_view_begin(const tecs_entity_view_t *view); +TECS_EXPORT const tecs_entity_t *Tecs_entity_view_end(const tecs_entity_view_t *view); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/inc/c_abi/Tecs_entity_view.hh b/inc/c_abi/Tecs_entity_view.hh new file mode 100644 index 0000000..7480bba --- /dev/null +++ b/inc/c_abi/Tecs_entity_view.hh @@ -0,0 +1,191 @@ +#pragma once + +#include "Tecs_entity.hh" +#include "Tecs_entity_view.h" + +#include +#include +#include + +namespace Tecs::abi { + class EntityView { + public: + typedef const Entity element_type; + typedef Entity value_type; + typedef uint64_t size_type; + typedef std::ptrdiff_t difference_type; + + typedef const Entity *pointer; + typedef const Entity &reference; + + class iterator { + public: + typedef std::ptrdiff_t difference_type; + typedef Entity value_type; + typedef const Entity *pointer; + typedef const Entity &reference; + typedef std::random_access_iterator_tag iterator_category; + + iterator(const tecs_entity_view_t &view, uint64_t index = 0) + : view(view), i(index), cachedBase(begin_uncached()), cacheCounter(cacheInvalidationCounter) {} + + inline const Entity *begin_uncached() const { + return reinterpret_cast(Tecs_entity_view_begin(&view)); + } + + inline const Entity *begin() const { + if (cacheInvalidationCounter != cacheCounter) { + cachedBase = begin_uncached(); + cacheCounter = cacheInvalidationCounter; + } + return cachedBase; + } + + inline reference operator*() const { +#ifndef TECS_UNCHECKED_MODE + if (i < view.start_index || i >= view.end_index) { + throw std::runtime_error("EntityView::iterator::operator*: index out of bounds"); + } +#endif + return begin()[i]; + } + + inline pointer operator->() const { +#ifndef TECS_UNCHECKED_MODE + if (i < view.start_index || i >= view.end_index) { + throw std::runtime_error("EntityView::iterator::operator->: index out of bounds"); + } +#endif + return begin() + i; + } + + inline reference operator[](difference_type n) const { + uint64_t index = i + n; +#ifndef TECS_UNCHECKED_MODE + if (index < view.start_index || index >= view.end_index) { + throw std::runtime_error("EntityView::iterator::operator[]: index out of bounds"); + } +#endif + return begin()[index]; + } + + inline iterator &operator++() noexcept { + i++; + return *this; + } + + inline iterator &operator--() noexcept { + i--; + return *this; + } + + inline iterator operator++(int) noexcept { + iterator tmp = *this; + i++; + return tmp; + } + + inline iterator operator--(int) noexcept { + iterator tmp = *this; + i--; + return tmp; + } + + inline iterator operator+(difference_type n) const noexcept { + return iterator(view, i + n); + } + + inline iterator operator-(difference_type n) const noexcept { + return iterator(view, i - n); + } + + inline iterator &operator+=(difference_type n) noexcept { + i += n; + return *this; + } + + inline iterator &operator-=(difference_type n) noexcept { + i -= n; + return *this; + } + + inline bool operator==(const iterator &other) const noexcept { + return view.storage == other.view.storage && i == other.i; + } + + inline bool operator!=(const iterator &other) const noexcept { + return view.storage != other.view.storage || i != other.i; + } + + const tecs_entity_view_t &view; + uint64_t i; + + mutable const Entity *cachedBase; + mutable size_t cacheCounter; + }; + + typedef std::reverse_iterator reverse_iterator; + + EntityView() {} + EntityView(const tecs_entity_view_t &view) : base(view) { +#ifndef TECS_UNCHECKED_MODE + if (view.storage == nullptr) { + throw std::runtime_error("EntityView storage is null"); + } + uint64_t storage_size = Tecs_entity_view_storage_size(&view); + if (view.start_index > storage_size) { + throw std::runtime_error("EntityView start index out of range: " + std::to_string(view.start_index)); + } else if (view.end_index > storage_size) { + throw std::runtime_error("EntityView end index out of range: " + std::to_string(view.end_index)); + } else if (view.start_index > view.end_index) { + throw std::runtime_error("EntityView start index is past end index: " + + std::to_string(view.start_index) + " > " + std::to_string(view.end_index)); + } +#endif + } + + inline iterator begin() const noexcept { + return iterator(base, base.start_index); + } + + inline iterator end() const noexcept { + return iterator(base, base.end_index); + } + + inline reverse_iterator rbegin() const noexcept { + return reverse_iterator(iterator{base, base.end_index}); + } + + inline reverse_iterator rend() const noexcept { + return reverse_iterator(iterator{base, base.start_index + 1}); + } + + inline reference operator[](size_type index) const { +#ifndef TECS_UNCHECKED_MODE + if (index < base.start_index || index >= base.end_index) { + throw std::runtime_error("EntityView index out of range: " + std::to_string(index)); + } +#endif + return reinterpret_cast(Tecs_entity_view_begin(&base))[index]; + } + + inline size_type size() const noexcept { + return base.end_index - base.start_index; + } + + inline bool empty() const noexcept { + return base.end_index <= base.start_index; + } + + inline EntityView subview(size_type offset, size_type count = std::numeric_limits::max()) const { +#ifndef TECS_UNCHECKED_MODE + if (base.storage == nullptr) throw std::runtime_error("EntityView::subview storage is null"); +#endif + return tecs_entity_view_t{base.storage, + base.start_index + offset, + std::min(base.end_index, base.start_index + offset + count)}; + } + + tecs_entity_view_t base; + }; +}; // namespace Tecs::abi diff --git a/inc/c_abi/Tecs_export.h b/inc/c_abi/Tecs_export.h new file mode 100644 index 0000000..e9f30fa --- /dev/null +++ b/inc/c_abi/Tecs_export.h @@ -0,0 +1,19 @@ +#pragma once + +#ifndef TECS_EXPORT + #ifdef TECS_SHARED_INTERNAL + /* We are building this library */ + #ifdef _WIN32 + #define TECS_EXPORT __declspec(dllexport) + #else + #define TECS_EXPORT __attribute__((__visibility__("default"))) + #endif + #else + /* We are using this library */ + #ifdef _WIN32 + #define TECS_EXPORT __declspec(dllimport) + #else + #define TECS_EXPORT + #endif + #endif +#endif diff --git a/inc/c_abi/Tecs_lock.h b/inc/c_abi/Tecs_lock.h new file mode 100644 index 0000000..336288d --- /dev/null +++ b/inc/c_abi/Tecs_lock.h @@ -0,0 +1,47 @@ +#pragma once + +#include "Tecs_entity.h" +#include "Tecs_entity_view.h" +#include "Tecs_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +static_assert(sizeof(bool) == 1, "Unexpected bool size"); + +typedef void tecs_lock_t; + +TECS_EXPORT uint64_t Tecs_lock_get_transaction_id(tecs_lock_t *dynLockPtr); +TECS_EXPORT bool Tecs_lock_is_add_remove_allowed(tecs_lock_t *dynLockPtr); +TECS_EXPORT bool Tecs_lock_is_write_allowed(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT bool Tecs_lock_is_read_allowed(tecs_lock_t *dynLockPtr, uint32_t componentIndex); + +TECS_EXPORT uint64_t Tecs_previous_entities_with(tecs_lock_t *dynLockPtr, uint32_t componentIndex, + tecs_entity_view_t *output); +TECS_EXPORT uint64_t Tecs_entities_with(tecs_lock_t *dynLockPtr, uint32_t componentIndex, tecs_entity_view_t *output); +TECS_EXPORT uint64_t Tecs_previous_entities(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output); +TECS_EXPORT uint64_t Tecs_entities(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output); +TECS_EXPORT tecs_entity_t Tecs_new_entity(tecs_lock_t *dynLockPtr); +TECS_EXPORT bool Tecs_has(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT bool Tecs_had(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT const void *Tecs_const_get(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT void *Tecs_get(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT const void *Tecs_get_previous(tecs_lock_t *dynLockPtr, uint32_t componentIndex); +TECS_EXPORT void *Tecs_set(tecs_lock_t *dynLockPtr, uint32_t componentIndex, const void *value); +TECS_EXPORT void Tecs_unset(tecs_lock_t *dynLockPtr, uint32_t componentIndex); + +// Observer Watch(); +// void StopWatching(Observer observer); + +// New lock must be released +TECS_EXPORT tecs_lock_t *Tecs_lock_read_only(tecs_lock_t *dynLockPtr); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/inc/c_abi/Tecs_lock.hh b/inc/c_abi/Tecs_lock.hh new file mode 100644 index 0000000..ffdb2fb --- /dev/null +++ b/inc/c_abi/Tecs_lock.hh @@ -0,0 +1,237 @@ +#pragma once + +#include "../Tecs_permissions.hh" +#include "Tecs_entity_view.hh" +#include "Tecs_lock.h" + +#include + +namespace Tecs::abi { + template typename ECSType, typename... AllComponentTypes, typename... Permissions> + class Lock, Permissions...> { + private: + using ECS = ECSType; + using LockType = Lock; + + mutable std::tuple cachedStorage; + mutable std::tuple cachedPreviousStorage; + mutable size_t cacheCounter; + + public: + std::shared_ptr base; + + inline Lock(const std::shared_ptr &lock) : base(lock) { + if constexpr (is_add_remove_allowed()) { + if (!Tecs_lock_is_add_remove_allowed(lock.get())) { + throw std::runtime_error("Lock is missing AddRemove permissions"); + } + } else { + uint32_t componentIndex = 0; + ( + [&] { + if constexpr (is_write_allowed()) { + if (!Tecs_lock_is_write_allowed(lock.get(), componentIndex)) { + throw std::runtime_error("Lock does not have " + + std::string(typeid(AllComponentTypes).name()) + + " write permissions"); + } + } + if constexpr (is_read_allowed()) { + if (!Tecs_lock_is_read_allowed(lock.get(), componentIndex)) { + throw std::runtime_error("Lock does not have " + + std::string(typeid(AllComponentTypes).name()) + + " read permissions"); + } + } + componentIndex++; + }(), + ...); + } + } + + // Returns true if this lock type can be constructed from a lock with the specified source permissions + template + static constexpr bool is_lock_subset() { + using SourceLockType = Lock; + if constexpr (is_add_remove_allowed() && !is_add_remove_allowed()) { + return false; + } else { + return std::conjunction...>(); + } + } + + // Returns true if this lock type has all of the requested permissions + template + static constexpr bool has_permissions() { + return Lock::template is_lock_subset(); + } + + // Reference an existing transaction + template(), int> = 0> + inline Lock(const Lock &source) : base(source.base) {} + + inline uint64_t GetTransactionId() const { + return Tecs_lock_get_transaction_id(base.get()); + } + + template + inline const EntityView PreviousEntitiesWith() const { + static_assert(!is_global_component(), "Entities can't have global components"); + + constexpr uint32_t componentIndex = ECS::template GetComponentIndex(); + tecs_entity_view_t view = {}; + (void)Tecs_previous_entities_with(base.get(), componentIndex, &view); + return EntityView(view); + } + + template + inline const EntityView EntitiesWith() const { + static_assert(!is_global_component(), "Entities can't have global components"); + + constexpr uint32_t componentIndex = ECS::template GetComponentIndex(); + tecs_entity_view_t view = {}; + (void)Tecs_entities_with(base.get(), componentIndex, &view); + return EntityView(view); + } + + inline const EntityView PreviousEntities() const { + tecs_entity_view_t view = {}; + (void)Tecs_previous_entities(base.get(), &view); + return EntityView(view); + } + + inline const EntityView Entities() const { + tecs_entity_view_t view = {}; + (void)Tecs_entities(base.get(), &view); + return EntityView(view); + } + + /** + * Creates a new entity with AddRemove lock permissions. + * + * Note: This function invalidates all references to components if a storage resize occurs. + */ + inline Entity NewEntity() const { + static_assert(is_add_remove_allowed(), "Lock does not have AddRemove permission."); + + cacheInvalidationCounter++; + + return Entity(Tecs_new_entity(base.get())); + } + + template + inline bool Has() const { + static_assert(all_global_components(), "Only global components can be accessed without an Entity"); + + return (Tecs_has(base.get(), ECS::template GetComponentIndex()) && ...); + } + + template + inline bool Had() const { + static_assert(all_global_components(), "Only global components can be accessed without an Entity"); + + return (Tecs_had(base.get(), ECS::template GetComponentIndex()) && ...); + } + + template, LockType>::value, T, const T>> + inline ReturnType &Get() const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + static_assert(is_write_allowed() || std::is_const(), + "Can't get non-const reference of read only Component."); + static_assert(is_global_component(), "Only global components can be accessed without an Entity"); + + constexpr uint32_t componentIndex = ECS::template GetComponentIndex(); + if constexpr (std::is_const()) { + return *static_cast(Tecs_const_get(base.get(), componentIndex)); + } else { + return *static_cast(Tecs_get(base.get(), componentIndex)); + } + } + + template + inline const T &GetPrevious() const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + static_assert(is_global_component(), "Only global components can be accessed without an Entity"); + + constexpr uint32_t componentIndex = ECS::template GetComponentIndex(); + return *static_cast(Tecs_get_previous(base.get(), componentIndex)); + } + + template + inline T &Set(T &value) const { + static_assert(is_write_allowed(), "Component is not locked for writing."); + static_assert(is_global_component(), "Only global components can be accessed without an Entity"); + + constexpr uint32_t componentIndex = ECS::template GetComponentIndex(); + return *static_cast(Tecs_set(base.get(), componentIndex, &value)); + } + + template + inline T &Set(Args &&...args) const { + static_assert(is_write_allowed(), "Component is not locked for writing."); + static_assert(is_global_component(), "Only global components can be accessed without an Entity"); + + T temp = T(std::forward(args)...); + + constexpr uint32_t componentIndex = ECS::template GetComponentIndex(); + return *static_cast(Tecs_set(base.get(), componentIndex, &temp)); + } + + template + inline void Unset() const { + static_assert(is_add_remove_allowed(), "Components cannot be removed without an AddRemove lock."); + static_assert(all_global_components(), "Only global components can be unset without an Entity"); + + (Tecs_unset(base.get(), ECS::template GetComponentIndex()), ...); + } + + // template + // inline Observer Watch() const { + // static_assert(is_add_remove_allowed(), "An AddRemove lock is required to watch for ecs + // changes."); + + // auto &observerList = instance.template Observers(); + // auto &eventList = observerList.observers.emplace_back(std::make_shared>()); + // return Observer(instance, eventList); + // } + + // template + // inline void StopWatching(Observer &observer) const { + // static_assert(is_add_remove_allowed(), "An AddRemove lock is required to stop an observer."); + // auto eventList = observer.eventListWeak.lock(); + // auto &observers = instance.template Observers().observers; + // observers.erase(std::remove(observers.begin(), observers.end(), eventList), observers.end()); + // observer.eventListWeak.reset(); + // } + + // template + // inline Lock Subset() const { + // using NewLockType = Lock; + // static_assert(has_permissions(), "Lock types are not a subset of existing permissions."); + + // return NewLockType(*this); + // } + + /** + * Convert this lock into a read-only variant. + * + * Reads performed through this lock will not be able to see writes from the parent lock, and instead will + * return the previous value. + */ + // inline auto ReadOnlySubset() const { + // using NewLockType = Lock::type_readonly>; static_assert(has_permissions(), "Lock types are not + // a subset of existing permissions."); return NewLockType(this->instance, this->base, {}); + // } + + // long UseCount() const { + // return base.use_count(); + // } + + private: + friend struct Entity; + }; +} // namespace Tecs::abi diff --git a/inc/c_abi/Tecs_tracing.h b/inc/c_abi/Tecs_tracing.h new file mode 100644 index 0000000..6c0f020 --- /dev/null +++ b/inc/c_abi/Tecs_tracing.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Tecs.h" +#include "Tecs_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef void tecs_perf_trace_t; + +TECS_EXPORT void Tecs_ecs_start_perf_trace(tecs_ecs_t *ecsPtr); +TECS_EXPORT tecs_perf_trace_t *Tecs_ecs_stop_perf_trace(tecs_ecs_t *ecsPtr); +TECS_EXPORT void Tecs_perf_trace_set_thread_name(tecs_perf_trace_t *tracePtr, size_t threadIdHash, + const char *threadName); +TECS_EXPORT size_t Tecs_perf_trace_get_thread_name(tecs_perf_trace_t *tracePtr, size_t threadIdHash, size_t bufferSize, + char *output); +TECS_EXPORT void Tecs_perf_trace_save_to_csv(tecs_perf_trace_t *tracePtr, const char *filePath); +TECS_EXPORT void Tecs_ecs_perf_trace_release(tecs_perf_trace_t *tracePtr); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/inc/c_abi/Tecs_tracing.hh b/inc/c_abi/Tecs_tracing.hh new file mode 100644 index 0000000..79817b0 --- /dev/null +++ b/inc/c_abi/Tecs_tracing.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "Tecs_tracing.h" + +#include +#include +#include + +namespace Tecs::abi { + struct PerformanceTrace { + std::shared_ptr base; + + void SetThreadName(const std::string &name, size_t threadIdHash) { + Tecs_perf_trace_set_thread_name(base.get(), threadIdHash, name.c_str()); + } + + void SetThreadName(const std::string &name, std::thread::id threadId = std::this_thread::get_id()) { + static const std::hash threadHasher; + SetThreadName(name, threadHasher(threadId)); + } + + std::string GetThreadName(size_t threadIdHash) { + size_t size = Tecs_perf_trace_get_thread_name(base.get(), threadIdHash, 0, nullptr); + std::string str(size, '\0'); + Tecs_perf_trace_get_thread_name(base.get(), threadIdHash, size, str.data()); + return str; + } + + std::string GetThreadName(std::thread::id threadId = std::this_thread::get_id()) { + static const std::hash threadHasher; + return GetThreadName(threadHasher(threadId)); + } + + void SaveToCSV(const std::string &filePath) { + Tecs_perf_trace_save_to_csv(base.get(), filePath.c_str()); + } + }; +} // namespace Tecs::abi diff --git a/src/Tecs.cc b/src/Tecs.cc index 662f5ed..497d577 100644 --- a/src/Tecs.cc +++ b/src/Tecs.cc @@ -2,8 +2,8 @@ namespace Tecs { // Used for detecting nested transactions - thread_local std::array activeTransactions; + thread_local std::array activeTransactions; thread_local size_t activeTransactionsCount = 0; - std::atomic_size_t nextEcsId(0); - std::atomic_size_t nextTransactionId(0); + std::atomic_uint64_t nextEcsId(0); + std::atomic_uint64_t nextTransactionId(0); } // namespace Tecs diff --git a/src/c_abi/Tecs_entity_view.cc b/src/c_abi/Tecs_entity_view.cc new file mode 100644 index 0000000..aa95ce3 --- /dev/null +++ b/src/c_abi/Tecs_entity_view.cc @@ -0,0 +1,25 @@ + +#include +#include + +extern "C" { + +TECS_EXPORT uint64_t Tecs_entity_view_storage_size(const tecs_entity_view_t *view) { + auto *storage = static_cast(view->storage); + return storage->size(); +} + +TECS_EXPORT const tecs_entity_t *Tecs_entity_view_begin(const tecs_entity_view_t *view) { + static_assert(sizeof(tecs_entity_t) == sizeof(Tecs::Entity)); + auto *storage = static_cast(view->storage); + if (storage->size() == 0) return nullptr; + return reinterpret_cast(&*storage->begin()); +} + +TECS_EXPORT const tecs_entity_t *Tecs_entity_view_end(const tecs_entity_view_t *view) { + static_assert(sizeof(tecs_entity_t) == sizeof(Tecs::Entity)); + auto *storage = static_cast(view->storage); + if (storage->size() == 0) return nullptr; + return reinterpret_cast(&*storage->end()); +} +} // extern "C" diff --git a/src/c_abi/Tecs_tracing.cc b/src/c_abi/Tecs_tracing.cc new file mode 100644 index 0000000..9718107 --- /dev/null +++ b/src/c_abi/Tecs_tracing.cc @@ -0,0 +1,60 @@ +#ifdef TECS_ENABLE_PERFORMANCE_TRACING + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS + #endif + + #include + #include + #include + #include + + #ifdef TECS_C_ABI_ECS_INCLUDE + #include TECS_C_ABI_ECS_INCLUDE + #endif + + #ifdef TECS_C_ABI_ECS_NAME +using ECS = TECS_C_ABI_ECS_NAME; + #else +using ECS = Tecs::ECS<>; + #endif + +extern "C" { + +TECS_EXPORT void Tecs_ecs_start_perf_trace(tecs_ecs_t *ecsPtr) { + ECS *ecs = static_cast(ecsPtr); + ecs->StartTrace(); +} + +TECS_EXPORT tecs_perf_trace_t *Tecs_ecs_stop_perf_trace(tecs_ecs_t *ecsPtr) { + ECS *ecs = static_cast(ecsPtr); + return new Tecs::PerformanceTrace(ecs->StopTrace()); +} + +TECS_EXPORT void Tecs_perf_trace_set_thread_name(tecs_perf_trace_t *tracePtr, size_t threadIdHash, + const char *threadName) { + Tecs::PerformanceTrace *trace = static_cast(tracePtr); + trace->SetThreadName(std::string(threadName), threadIdHash); +} + +TECS_EXPORT size_t Tecs_perf_trace_get_thread_name(tecs_perf_trace_t *tracePtr, size_t threadIdHash, size_t bufferSize, + char *output) { + Tecs::PerformanceTrace *trace = static_cast(tracePtr); + std::string name = trace->GetThreadName(threadIdHash); + if (name.size() < bufferSize) { + (void)std::strncpy(output, name.c_str(), bufferSize); + } + return name.size() + 1; +} + +TECS_EXPORT void Tecs_perf_trace_save_to_csv(tecs_perf_trace_t *tracePtr, const char *filePath) { + Tecs::PerformanceTrace *trace = static_cast(tracePtr); + trace->SaveToCSV(std::string(filePath)); +} + +TECS_EXPORT void Tecs_ecs_perf_trace_release(tecs_perf_trace_t *tracePtr) { + Tecs::PerformanceTrace *trace = static_cast(tracePtr); + delete trace; +} + +} // extern "C" +#endif diff --git a/src/c_abi/codegen/gen_common.hh b/src/c_abi/codegen/gen_common.hh new file mode 100644 index 0000000..5b180da --- /dev/null +++ b/src/c_abi/codegen/gen_common.hh @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define STRING(s) #s +#define STRINGIFY(s) STRING(s) + +#ifdef TECS_C_ABI_ECS_IMPL_INCLUDE + #include TECS_C_ABI_ECS_IMPL_INCLUDE +#elif defined(TECS_C_ABI_ECS_INCLUDE) + #include TECS_C_ABI_ECS_INCLUDE +#endif +#ifndef TECS_C_ABI_ECS_NAME +using ECS = Tecs::ECS<>; + #define TECS_C_ABI_ECS_NAME ECS +#endif +#ifndef TECS_C_ABI_TYPE_PREFIX + #define TECS_C_ABI_TYPE_PREFIX "" +#endif + +template +auto EmbedTypeIntoSignature() { + const char *funcName = std::source_location::current().function_name(); + return std::string_view(std::strcmp("EmbedTypeIntoSignature", funcName) == 0 ? typeid(T).name() : funcName); +} + +template +auto TypeToString() { + auto dummyChar = EmbedTypeIntoSignature(); + auto charStart = dummyChar.find("unsigned char"); + auto tailLength = dummyChar.size() - charStart - std::string("unsigned char").size(); + + auto typeStart = charStart; + auto embeddingSignature = EmbedTypeIntoSignature(); + auto enumStart = embeddingSignature.find("enum ", charStart); + if (enumStart == charStart) typeStart += std::string("enum ").length(); + auto classStart = embeddingSignature.find("class ", charStart); + if (classStart == charStart) typeStart += std::string("class ").length(); + auto structStart = embeddingSignature.find("struct ", charStart); + if (structStart == charStart) typeStart += std::string("struct ").length(); + + auto typeLength = embeddingSignature.size() - typeStart - tailLength; + return embeddingSignature.substr(typeStart, typeLength); +} + +std::string SnakeCaseTypeName(std::string_view name) { + std::string snakeCaseName; + bool wasCaps = true; + bool wasSep = false; + for (const char &ch : name) { + if (ch != std::tolower(ch)) { + if (!wasCaps) snakeCaseName.append(1, '_'); + wasCaps = true; + } else { + wasCaps = false; + } + if (ch == ':') { + if (!wasSep) snakeCaseName.append(1, '_'); + wasSep = true; + wasCaps = true; + } else { + wasSep = false; + } + snakeCaseName.append(1, (char)std::tolower(ch)); + } + return snakeCaseName; +} + +template +std::string TypeToCName() { + if constexpr (std::is_enum()) { + if constexpr (sizeof(T) == sizeof(int)) { + return std::string("enum ") + TECS_C_ABI_TYPE_PREFIX + + SnakeCaseTypeName(ECS::template GetComponentName()) + "_t"; + } else { + return std::string(TypeToString>()); + } + } else { + return TECS_C_ABI_TYPE_PREFIX + SnakeCaseTypeName(ECS::template GetComponentName()) + "_t"; + } +} + +template +struct CodeGenerator; + +template typename ECSType, typename... AllComponentTypes> +struct CodeGenerator> { + using ECS = ECSType; + + static constexpr std::array GetComponentNames() { + return { + TypeToString()..., + }; + } + + static constexpr std::array GetComponentSnakeCaseNames() { + return { + SnakeCaseTypeName(ECS::template GetComponentName())..., + }; + } + + static constexpr std::array GetComponentCTypeName() { + return { + TypeToCName()..., + }; + } + + static constexpr std::array GetComponentGlobalList() { + return { + Tecs::is_global_component()..., + }; + } + + static constexpr std::array GetComponentCopyableList() { + return { + std::is_copy_constructible()..., + }; + } +}; diff --git a/src/c_abi/codegen/gen_ecs.hh b/src/c_abi/codegen/gen_ecs.hh new file mode 100644 index 0000000..8e352c1 --- /dev/null +++ b/src/c_abi/codegen/gen_ecs.hh @@ -0,0 +1,102 @@ +#pragma once + +#include "gen_common.hh" + +template +void generateECSCC(S &out) { + auto names = CodeGenerator::GetComponentNames(); + out << R"RAWSTR( +TECS_EXPORT tecs_ecs_t *Tecs_make_ecs_instance() { + return new ECS(); +} + +TECS_EXPORT void Tecs_release_ecs_instance(tecs_ecs_t *ecsPtr) { + ECS *instance = static_cast(ecsPtr); + delete instance; +} + +TECS_EXPORT tecs_lock_t *Tecs_ecs_start_transaction(tecs_ecs_t *ecsPtr, uint64_t readPermissions, uint64_t writePermissions) { + ECS *instance = static_cast(ecsPtr); + if constexpr (1 + ECS::GetComponentCount() > std::numeric_limits::digits) { + std::cerr << "Too many components to use uint64 init: " << ECS::GetComponentCount() << std::endl; + return nullptr; + } else { + return new DynamicLock(*instance, + DynamicLock::PermissionBitset(readPermissions), + DynamicLock::PermissionBitset(writePermissions)); + } +} + +TECS_EXPORT tecs_lock_t *Tecs_ecs_start_transaction_bitstr(tecs_ecs_t *ecsPtr, const char *readPermissions, + const char *writePermissions) { + ECS *instance = static_cast(ecsPtr); + return new DynamicLock(*instance, + DynamicLock::PermissionBitset(std::string(readPermissions)), + DynamicLock::PermissionBitset(std::string(writePermissions))); +} + +TECS_EXPORT uint64_t Tecs_ecs_get_instance_id(tecs_ecs_t *ecsPtr) { + ECS *instance = static_cast(ecsPtr); + return instance->GetInstanceId(); +} + +TECS_EXPORT uint64_t Tecs_ecs_get_next_transaction_id(tecs_ecs_t *ecsPtr) { + ECS *instance = static_cast(ecsPtr); + return instance->GetNextTransactionId(); +} + +TECS_EXPORT uint32_t Tecs_ecs_get_component_count() { + return ECS::GetComponentCount(); +} + +TECS_EXPORT size_t Tecs_ecs_get_component_size(uint32_t componentIndex) { + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + out << " return sizeof(" << names[i] << ");" << std::endl; + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return 0; + } +} + +TECS_EXPORT size_t Tecs_ecs_get_component_name(uint32_t componentIndex, size_t bufferSize, char *output) { + std::string name; + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + out << " name = ECS::GetComponentName<" << names[i] << ">();" << std::endl; + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return 0; + } + if (name.size() < bufferSize) { + (void)std::strncpy(output, name.c_str(), bufferSize); + } + return name.size() + 1; +} + +TECS_EXPORT size_t Tecs_ecs_get_bytes_per_entity() { + return ECS::GetBytesPerEntity(); +} + +TECS_EXPORT void Tecs_lock_release(tecs_lock_t *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + delete dynLock; +} +)RAWSTR"; +} diff --git a/src/c_abi/codegen/gen_entity.hh b/src/c_abi/codegen/gen_entity.hh new file mode 100644 index 0000000..ec5c8fb --- /dev/null +++ b/src/c_abi/codegen/gen_entity.hh @@ -0,0 +1,441 @@ +#pragma once + +#include "gen_common.hh" + +template +void generateEntityH(S &out) { + auto snakeCaseNames = CodeGenerator::GetComponentSnakeCaseNames(); + auto cnames = CodeGenerator::GetComponentCTypeName(); + auto globalList = CodeGenerator::GetComponentGlobalList(); + for (size_t i = 0; i < snakeCaseNames.size(); i++) { + if (globalList[i]) continue; + auto &scn = snakeCaseNames[i]; + auto &cname = cnames[i]; + out << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_get_entity_" << scn << "_storage(tecs_lock_t *dynLockPtr);" + << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_get_previous_entity_" << scn + << "_storage(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT bool Tecs_entity_has_" << scn << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity);" + << std::endl; + out << "TECS_EXPORT bool Tecs_entity_had_" << scn << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity);" + << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_entity_const_get_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity);" << std::endl; + out << "TECS_EXPORT " << cname << " *Tecs_entity_get_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity);" << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_entity_get_previous_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity);" << std::endl; + out << "TECS_EXPORT " << cname << " *Tecs_entity_set_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity, const " << cname << " *value);" << std::endl; + out << "TECS_EXPORT void Tecs_entity_unset_" << scn << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity);" + << std::endl; + } +} + +template +void generateEntityCC(S &out) { + auto names = CodeGenerator::GetComponentNames(); + auto snakeCaseNames = CodeGenerator::GetComponentSnakeCaseNames(); + auto cnames = CodeGenerator::GetComponentCTypeName(); + auto globalList = CodeGenerator::GetComponentGlobalList(); + auto copyableList = CodeGenerator::GetComponentCopyableList(); + out << R"RAWSTR( +TECS_EXPORT const void *Tecs_get_entity_storage(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " read permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return lock->GetStorage();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT const void *Tecs_get_previous_entity_storage(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " read permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return lock->GetPreviousStorage<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT bool Tecs_entity_exists(tecs_lock_t *dynLockPtr, tecs_entity_t entity) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return Tecs::Entity(entity).Exists(*dynLock); +} + +TECS_EXPORT bool Tecs_entity_existed(tecs_lock_t *dynLockPtr, tecs_entity_t entity) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return Tecs::Entity(entity).Existed(*dynLock); +} + +TECS_EXPORT bool Tecs_entity_has(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return false;" << std::endl; + } else { + out << " return Tecs::Entity(entity).Has<" << names[i] << ">(*dynLock);" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +TECS_EXPORT bool Tecs_entity_has_bitset(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint64_t componentBits) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return Tecs::Entity(entity).HasBitset>(*dynLock, DynamicLock::PermissionBitset(componentBits)); +} + +TECS_EXPORT bool Tecs_entity_had(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return false;" << std::endl; + } else { + out << " return Tecs::Entity(entity).Had<" << names[i] << ">(*dynLock);" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +TECS_EXPORT bool Tecs_entity_had_bitset(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint64_t componentBits) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return Tecs::Entity(entity).HadBitset>(*dynLock, DynamicLock::PermissionBitset(componentBits)); +} + +TECS_EXPORT const void *Tecs_entity_const_get(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex) { +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + out << " auto *storage = static_cast(Tecs_get_entity_storage(dynLockPtr, componentIndex));" << std::endl; + out << " if (!storage) return nullptr;" << std::endl; + out << " return &storage[Tecs::Entity(entity).index];" << std::endl; + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT void *Tecs_entity_get(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return &Tecs::Entity(entity).Get<" << names[i] << ">(*lock1);" << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &Tecs::Entity(entity).Get<" << names[i] << ">(*lock2);" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT const void *Tecs_entity_get_previous(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex) { +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + out << " auto *storage = static_cast(Tecs_get_previous_entity_storage(dynLockPtr, componentIndex));" << std::endl; + out << " if (!storage) return nullptr;" << std::endl; + out << " return &storage[Tecs::Entity(entity).index];" << std::endl; + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT void *Tecs_entity_set(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex, const void *value) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else if (!copyableList[i]) { + out << " std::cerr << \"Error: Can't set component type unless it is trivially copyable: " + << names[i] << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return &Tecs::Entity(entity).Set<" << names[i] << ">(*lock1, *static_cast(value));" << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &Tecs::Entity(entity).Set<" << names[i] << ">(*lock2, *static_cast(value));" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT void Tecs_entity_unset(tecs_lock_t *dynLockPtr, tecs_entity_t entity, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Error: Entities can't have global components: " << names[i] + << "\" << std::endl;" << std::endl; + } else { + out << " auto lock = dynLock->TryLock();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have AddRemove permissions\" << std::endl;" + << std::endl; + out << " return;" << std::endl; + out << " }" << std::endl; + out << " Tecs::Entity(entity).Unset<" << names[i] << ">(*lock);" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Error: Component index out of range: " << componentIndex << std::endl; + } +} + +TECS_EXPORT void Tecs_entity_destroy(tecs_lock_t *dynLockPtr, tecs_entity_t entity) { + DynamicLock *dynLock = static_cast(dynLockPtr); + auto lock = dynLock->TryLock(); + if (!lock) { + std::cerr << "Error: Lock does not have AddRemove permissions" << std::endl; + } else { + Tecs::Entity(entity).Destroy(*lock); + } +} +)RAWSTR"; + for (size_t i = 0; i < snakeCaseNames.size(); i++) { + if (globalList[i]) continue; + auto &scn = snakeCaseNames[i]; + auto &cname = cnames[i]; + out << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_get_entity_" << scn << "_storage(tecs_lock_t *dynLockPtr) {" + << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] << " read permissions\" << std::endl;" + << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return reinterpret_cast(lock->GetStorage());" + << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_get_previous_entity_" << scn + << "_storage(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] << " read permissions\" << std::endl;" + << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return reinterpret_cast(lock->GetPreviousStorage<" << names[i] << ">());" + << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_entity_get_previous_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity) {" << std::endl; + out << " auto *storage = Tecs_get_previous_entity_" << scn << "_storage(dynLockPtr);" << std::endl; + out << " if (!storage) return nullptr;" << std::endl; + out << " return &storage[Tecs::Entity(entity).index];" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT bool Tecs_entity_has_" << scn << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity) {" + << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " return Tecs::Entity(entity).Has<" << names[i] << ">(*dynLock);" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT bool Tecs_entity_had_" << scn << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity) {" + << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " return Tecs::Entity(entity).Had<" << names[i] << ">(*dynLock);" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT const " << cname << " *Tecs_entity_const_get_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity) {" << std::endl; + out << " auto *storage = Tecs_get_entity_" << scn << "_storage(dynLockPtr);" << std::endl; + out << " if (!storage) return nullptr;" << std::endl; + out << " return &storage[Tecs::Entity(entity).index];" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT " << cname << " *Tecs_entity_get_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return reinterpret_cast<" << cname << " *>(&Tecs::Entity(entity).Get<" << names[i] + << ">(*lock1));" << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] << " write permissions\" << std::endl;" + << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return reinterpret_cast<" << cname << " *>(&Tecs::Entity(entity).Get<" << names[i] << ">(*lock2));" + << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT " << cname << " *Tecs_entity_set_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity, const " << cname << " *value) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + if (!copyableList[i]) { + out << " std::cerr << \"Error: Can't set component type unless it is trivially copyable: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return reinterpret_cast<" << cname << " *>(&Tecs::Entity(entity).Set<" << names[i] + << ">(*lock1, *reinterpret_cast(value)));" << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return reinterpret_cast<" << cname << " *>(&Tecs::Entity(entity).Set<" << names[i] + << ">(*lock2, *reinterpret_cast(value)));" << std::endl; + } + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT void Tecs_entity_unset_" << scn << "(tecs_lock_t *dynLockPtr, tecs_entity_t entity) {" + << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock = dynLock->TryLock();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have AddRemove permissions\" << std::endl;" << std::endl; + out << " return;" << std::endl; + out << " }" << std::endl; + out << " Tecs::Entity(entity).Unset<" << names[i] << ">(*lock);" << std::endl; + out << "}" << std::endl; + out << std::endl; + } +} diff --git a/src/c_abi/codegen/gen_lock.hh b/src/c_abi/codegen/gen_lock.hh new file mode 100644 index 0000000..46b5c00 --- /dev/null +++ b/src/c_abi/codegen/gen_lock.hh @@ -0,0 +1,522 @@ +#pragma once + +#include "gen_common.hh" + +template +void generateLockH(S &out) { + auto snakeCaseNames = CodeGenerator::GetComponentSnakeCaseNames(); + auto globalList = CodeGenerator::GetComponentGlobalList(); + for (size_t i = 0; i < snakeCaseNames.size(); i++) { + auto &scn = snakeCaseNames[i]; + out << std::endl; + out << "TECS_EXPORT bool Tecs_lock_is_write_" << scn << "_allowed(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT bool Tecs_lock_is_read_" << scn << "_allowed(tecs_lock_t *dynLockPtr);" << std::endl; + + out << std::endl; + if (!globalList[i]) { + + out << "TECS_EXPORT uint64_t Tecs_previous_entities_with_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output);" << std::endl; + out << "TECS_EXPORT uint64_t Tecs_entities_with_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output);" << std::endl; + } else { + out << "TECS_EXPORT bool Tecs_has_" << scn << "(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT bool Tecs_had_" << scn << "(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT const void *Tecs_const_get_" << scn << "(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT void *Tecs_get_" << scn << "(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT const void *Tecs_get_previous_" << scn << "(tecs_lock_t *dynLockPtr);" << std::endl; + out << "TECS_EXPORT void *Tecs_set_" << scn << "(tecs_lock_t *dynLockPtr, const void *value);" << std::endl; + out << "TECS_EXPORT void Tecs_unset_" << scn << "(tecs_lock_t *dynLockPtr);" << std::endl; + } + } +} + +template +void generateLockCC(S &out) { + auto names = CodeGenerator::GetComponentNames(); + auto snakeCaseNames = CodeGenerator::GetComponentSnakeCaseNames(); + auto globalList = CodeGenerator::GetComponentGlobalList(); + out << R"RAWSTR( +TECS_EXPORT uint64_t Tecs_lock_get_transaction_id(tecs_lock_t *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return dynLock->GetTransactionId(); +} + +TECS_EXPORT bool Tecs_lock_is_add_remove_allowed(tecs_lock_t *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return dynLock->TryLock().has_value(); +} + +TECS_EXPORT bool Tecs_lock_is_write_allowed(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + out << " return dynLock->TryLock>().has_value();" << std::endl; + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +TECS_EXPORT bool Tecs_lock_is_read_allowed(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + out << " return dynLock->TryLock>().has_value();" << std::endl; + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +TECS_EXPORT uint64_t Tecs_previous_entities_with(tecs_lock_t *dynLockPtr, uint32_t componentIndex, tecs_entity_view_t *output) { + DynamicLock *dynLock = static_cast(dynLockPtr); + Tecs::EntityView view; + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Entities can't have global components: " << names[i] << "\" << std::endl;" + << std::endl; + out << " return 0;" << std::endl; + } else { + out << " view = dynLock->PreviousEntitiesWith<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return 0; + } + *output = tecs_entity_view_t { + .storage = view.storage, + .start_index = view.start_index, + .end_index = view.end_index, + }; + return view.size(); +} + +TECS_EXPORT uint64_t Tecs_entities_with(tecs_lock_t *dynLockPtr, uint32_t componentIndex, tecs_entity_view_t *output) { + DynamicLock *dynLock = static_cast(dynLockPtr); + Tecs::EntityView view; + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (globalList[i]) { + out << " std::cerr << \"Entities can't have global components: " << names[i] << "\" << std::endl;" + << std::endl; + out << " return 0;" << std::endl; + } else { + out << " view = dynLock->EntitiesWith<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return 0; + } + *output = tecs_entity_view_t { + .storage = view.storage, + .start_index = view.start_index, + .end_index = view.end_index, + }; + return view.size(); +} + +TECS_EXPORT uint64_t Tecs_previous_entities(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output) { + DynamicLock *dynLock = static_cast(dynLockPtr); + auto view = dynLock->PreviousEntities(); + *output = tecs_entity_view_t { + .storage = view.storage, + .start_index = view.start_index, + .end_index = view.end_index, + }; + return view.size(); +} + +TECS_EXPORT uint64_t Tecs_entities(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output) { + DynamicLock *dynLock = static_cast(dynLockPtr); + auto view = dynLock->Entities(); + *output = tecs_entity_view_t { + .storage = view.storage, + .start_index = view.start_index, + .end_index = view.end_index, + }; + return view.size(); +} + +TECS_EXPORT tecs_entity_t Tecs_new_entity(tecs_lock_t *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + auto lock = dynLock->TryLock(); + if (!lock) { + std::cerr << "Error: Lock does not have AddRemove permissions" << std::endl; + return 0; + } + return (tecs_entity_t)lock->NewEntity(); +} + +TECS_EXPORT bool Tecs_has(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return false;" << std::endl; + } else { + out << " return dynLock->Has<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +TECS_EXPORT bool Tecs_had(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return false;" << std::endl; + } else { + out << " return dynLock->Had<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +TECS_EXPORT const void *Tecs_const_get(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " read permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock->Get();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT void *Tecs_get(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return &lock1->Get<" << names[i] << ">();" << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock2->Get<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT const void *Tecs_get_previous(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " read permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock->GetPrevious();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT void *Tecs_set(tecs_lock_t *dynLockPtr, uint32_t componentIndex, const void *value) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + } else { + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return &lock1->Set<" << names[i] << ">(*static_cast(value));" + << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock2->Set<" << names[i] << ">(*static_cast(value));" + << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +TECS_EXPORT void Tecs_unset(tecs_lock_t *dynLockPtr, uint32_t componentIndex) { + DynamicLock *dynLock = static_cast(dynLockPtr); + // For each component... +)RAWSTR"; + for (size_t i = 0; i < names.size(); i++) { + if (i == 0) { + out << " if (componentIndex == 0) {" << std::endl; + } else { + out << " } else if (componentIndex == " << i << ") {" << std::endl; + } + if (!globalList[i]) { + out << " std::cerr << \"Only global components can be accessed without an Entity: " << names[i] + << "\" << std::endl;" << std::endl; + } else { + out << " auto lock = dynLock->TryLock();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have AddRemove permissions\" << std::endl;" + << std::endl; + out << " return;" << std::endl; + out << " }" << std::endl; + out << " lock->Unset<" << names[i] << ">();" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + } +} +)RAWSTR"; + for (size_t i = 0; i < snakeCaseNames.size(); i++) { + auto &scn = snakeCaseNames[i]; + out << std::endl; + out << "TECS_EXPORT bool Tecs_lock_is_write_" << scn << "_allowed(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " return dynLock->TryLock>().has_value();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT bool Tecs_lock_is_read_" << scn << "_allowed(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " return dynLock->TryLock>().has_value();" << std::endl; + out << "}" << std::endl; + out << std::endl; + + if (!globalList[i]) { + out << "TECS_EXPORT uint64_t Tecs_previous_entities_with_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " Tecs::EntityView view = dynLock->PreviousEntitiesWith<" << names[i] << ">();" << std::endl; + out << " *output = tecs_entity_view_t{" << std::endl; + out << " .storage = view.storage," << std::endl; + out << " .start_index = view.start_index," << std::endl; + out << " .end_index = view.end_index," << std::endl; + out << " };" << std::endl; + out << " return view.size();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT uint64_t Tecs_entities_with_" << scn + << "(tecs_lock_t *dynLockPtr, tecs_entity_view_t *output) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " Tecs::EntityView view = dynLock->EntitiesWith<" << names[i] << ">();" << std::endl; + out << " *output = tecs_entity_view_t{" << std::endl; + out << " .storage = view.storage," << std::endl; + out << " .start_index = view.start_index," << std::endl; + out << " .end_index = view.end_index," << std::endl; + out << " };" << std::endl; + out << " return view.size();" << std::endl; + out << "}" << std::endl; + } else { + out << "TECS_EXPORT bool Tecs_has_" << scn << "(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " return dynLock->Has<" << names[i] << ">();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT bool Tecs_had_" << scn << "(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " return dynLock->Had<" << names[i] << ">();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT const void *Tecs_const_get_" << scn << "(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " read permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock->Get();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT void *Tecs_get_" << scn << "(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return &lock1->Get<" << names[i] << ">();" << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock2->Get<" << names[i] << ">();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT const void *Tecs_get_previous_" << scn << "(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock = dynLock->TryLock>();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " read permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock->GetPrevious();" << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT void *Tecs_set_" << scn << "(tecs_lock_t *dynLockPtr, const void *value) {" + << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock1 = dynLock->TryLock();" << std::endl; + out << " if (lock1) {" << std::endl; + out << " return &lock1->Set<" << names[i] << ">(*static_cast(value));" + << std::endl; + out << " }" << std::endl; + out << " auto lock2 = dynLock->TryLock>();" << std::endl; + out << " if (!lock2) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have " << names[i] + << " write permissions\" << std::endl;" << std::endl; + out << " return nullptr;" << std::endl; + out << " }" << std::endl; + out << " return &lock2->Set<" << names[i] << ">(*static_cast(value));" + << std::endl; + out << "}" << std::endl; + out << std::endl; + out << "TECS_EXPORT void Tecs_unset_" << scn << "(tecs_lock_t *dynLockPtr) {" << std::endl; + out << " DynamicLock *dynLock = static_cast(dynLockPtr);" << std::endl; + out << " auto lock = dynLock->TryLock();" << std::endl; + out << " if (!lock) {" << std::endl; + out << " std::cerr << \"Error: Lock does not have AddRemove permissions\" << std::endl;" + << std::endl; + out << " return;" << std::endl; + out << " }" << std::endl; + out << " lock->Unset<" << names[i] << ">();" << std::endl; + out << "}" << std::endl; + } + } + out << R"RAWSTR( +// Observer Watch(); +// void StopWatching(Observer observer); + +TECS_EXPORT tecs_lock_t *Tecs_lock_read_only(tecs_lock_t *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return new DynamicLock(dynLock->ReadOnlySubset()); +} +)RAWSTR"; +} diff --git a/src/c_abi/codegen/gen_main.cc b/src/c_abi/codegen/gen_main.cc new file mode 100644 index 0000000..8909c0e --- /dev/null +++ b/src/c_abi/codegen/gen_main.cc @@ -0,0 +1,86 @@ +#include "gen_ecs.hh" +#include "gen_entity.hh" +#include "gen_lock.hh" + +int main(int argc, char **argv) { + if (argc != 3) { + std::cerr << "Usage: codegen out/ecs.h out/ecs.cc" << std::endl; + return -1; + } + { + auto out = std::ofstream(argv[1], std::ios::trunc); + out << R"RAWSTR(/* + * THIS FILE IS AUTO-GENERATED -- DO NOT EDIT + * See src/c_abi/codegen/gen_main.cc to modify + */ + +#pragma once + +#include "c_abi/Tecs_entity.h" +#include "c_abi/Tecs_entity_view.h" +#include "c_abi/Tecs_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +static_assert(sizeof(bool) == 1, "Unexpected bool size"); +)RAWSTR"; +#ifdef TECS_C_ABI_ECS_INCLUDE + out << "#include " STRINGIFY(TECS_C_ABI_ECS_INCLUDE) << std::endl; +#endif + out << R"RAWSTR( +typedef void tecs_lock_t; +typedef uint64_t tecs_entity_t; +)RAWSTR"; + generateLockH(out); + generateEntityH(out); + out << R"RAWSTR( +#ifdef __cplusplus +} +#endif +)RAWSTR"; + } + { + auto out = std::ofstream(argv[2], std::ios::trunc); + out << R"RAWSTR(/* + * THIS FILE IS AUTO-GENERATED -- DO NOT EDIT + * See src/c_abi/codegen/gen_main.cc to modify + */ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS +#endif + +)RAWSTR"; +#ifdef TECS_C_ABI_ECS_IMPL_INCLUDE + out << "#include " STRINGIFY(TECS_C_ABI_ECS_IMPL_INCLUDE) << std::endl; +#elif defined(TECS_C_ABI_ECS_INCLUDE) + out << "#include " STRINGIFY(TECS_C_ABI_ECS_INCLUDE) << std::endl; +#endif + out << R"RAWSTR( +#include +#include +#include +#include + +)RAWSTR"; + out << "using ECS = " << TypeToString(); + out << R"RAWSTR(; +using DynamicLock = Tecs::DynamicLock; + +extern "C" { +)RAWSTR"; + generateECSCC(out); + generateEntityCC(out); + generateLockCC(out); + out << R"RAWSTR( +} // extern "C" +)RAWSTR"; + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e691673..bafc996 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,67 @@ -add_executable(${PROJECT_NAME}-benchmark benchmark.cpp transform_component.cpp) -target_link_libraries(${PROJECT_NAME}-benchmark ${PROJECT_NAME}) -target_compile_definitions(${PROJECT_NAME}-benchmark PRIVATE TECS_ENABLE_PERFORMANCE_TRACING TECS_UNCHECKED_MODE) +include(CheckIPOSupported) +check_ipo_supported(RESULT lto_supported OUTPUT lto_error) + +add_executable(${PROJECT_NAME}-benchmark + benchmark.cpp +) +target_link_libraries(${PROJECT_NAME}-benchmark Tecs) +target_include_directories( + ${PROJECT_NAME}-benchmark + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_compile_definitions(${PROJECT_NAME}-benchmark PRIVATE TECS_UNCHECKED_MODE TECS_ENABLE_PERFORMANCE_TRACING) if(WIN32) # Link winmm library for timeBeginPeriod() target_link_libraries(${PROJECT_NAME}-benchmark winmm) endif() +if(lto_supported) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "IPO / LTO enabled") + set_target_properties(${PROJECT_NAME}-benchmark PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() +else() + message(STATUS "IPO / LTO not supported: ${lto_error}") +endif() + +TecsGenerateCHeaders( + TARGET_NAME ${PROJECT_NAME}-benchmark-cabi-gen + HEADER_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}-benchmark-cabi-gen + ECS_INCLUDE_PATH "test_components.hh" + ECS_NAME testing::ECS + SOURCES + transform_component.cpp + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + COMPILE_DEFINITIONS + TECS_ENABLE_PERFORMANCE_TRACING +) + +add_executable(${PROJECT_NAME}-benchmark-cabi + benchmark.cpp +) +target_link_libraries(${PROJECT_NAME}-benchmark-cabi ${PROJECT_NAME}-benchmark-cabi-gen) +target_include_directories( + ${PROJECT_NAME}-benchmark-cabi + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_compile_definitions(${PROJECT_NAME}-benchmark-cabi PRIVATE BENCHMARK_CABI TECS_UNCHECKED_MODE TECS_ENABLE_PERFORMANCE_TRACING) +if(WIN32) + # Link winmm library for timeBeginPeriod() + target_link_libraries(${PROJECT_NAME}-benchmark-cabi winmm) +endif() + +if(lto_supported) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "IPO / LTO enabled") + set_target_properties(${PROJECT_NAME}-benchmark-cabi PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() +else() + message(STATUS "IPO / LTO not supported: ${lto_error}") +endif() + # Only build the tracy benchmark if the submodule is populated if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tracy/CMakeLists.txt) set(TRACY_NO_EXIT ON CACHE BOOL "" FORCE) @@ -17,8 +73,15 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tracy/CMakeLists.txt) target_compile_options(TracyClient PRIVATE -Wno-unused-private-field -Wno-unused-function) endif() - add_executable(${PROJECT_NAME}-benchmark-tracy benchmark.cpp transform_component.cpp) - target_link_libraries(${PROJECT_NAME}-benchmark-tracy ${PROJECT_NAME} TracyClient) + add_executable(${PROJECT_NAME}-benchmark-tracy + benchmark.cpp + ) + target_link_libraries(${PROJECT_NAME}-benchmark-tracy TracyClient Tecs) + target_include_directories( + ${PROJECT_NAME}-benchmark-tracy + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) target_compile_definitions( ${PROJECT_NAME}-benchmark-tracy PRIVATE @@ -31,11 +94,91 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tracy/CMakeLists.txt) # Link winmm library for timeBeginPeriod() target_link_libraries(${PROJECT_NAME}-benchmark-tracy winmm) endif() + + if(lto_supported) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "IPO / LTO enabled") + set_target_properties(${PROJECT_NAME}-benchmark-tracy PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + else() + message(STATUS "IPO / LTO not supported: ${lto_error}") + endif() + + TecsGenerateCHeaders( + TARGET_NAME ${PROJECT_NAME}-benchmark-tracy-cabi-gen + HEADER_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}-benchmark-tracy-cabi-gen + ECS_INCLUDE_PATH "test_components.hh" + ECS_NAME testing::ECS + SOURCES + transform_component.cpp + LINK_LIBRARIES + TracyClient + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + COMPILE_DEFINITIONS + TECS_ENABLE_TRACY + TECS_TRACY_INCLUDE_LOCKS + TECS_TRACY_INCLUDE_DETAILED_COMMIT + TECS_UNCHECKED_MODE + ) + + add_executable(${PROJECT_NAME}-benchmark-tracy-cabi + benchmark.cpp + ) + target_link_libraries(${PROJECT_NAME}-benchmark-tracy-cabi ${PROJECT_NAME}-benchmark-tracy-cabi-gen TracyClient) + target_include_directories( + ${PROJECT_NAME}-benchmark-tracy-cabi + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) + target_compile_definitions( + ${PROJECT_NAME}-benchmark-tracy-cabi + PRIVATE + BENCHMARK_CABI + TECS_ENABLE_TRACY + TECS_TRACY_INCLUDE_LOCKS + TECS_TRACY_INCLUDE_DETAILED_COMMIT + TECS_UNCHECKED_MODE + ) + if(WIN32) + # Link winmm library for timeBeginPeriod() + target_link_libraries(${PROJECT_NAME}-benchmark-tracy-cabi winmm) + endif() + + if(lto_supported) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "IPO / LTO enabled") + set_target_properties(${PROJECT_NAME}-benchmark-tracy-cabi PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + else() + message(STATUS "IPO / LTO not supported: ${lto_error}") + endif() endif() -add_executable(${PROJECT_NAME}-tests tests.cpp transform_component.cpp) +TecsGenerateCHeaders( + TARGET_NAME ${PROJECT_NAME}-tests-cabi-gen + HEADER_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}-tests-cabi-gen + ECS_INCLUDE_PATH "test_components.hh" + ECS_NAME testing::ECS + SOURCES + transform_component.cpp + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_executable(${PROJECT_NAME}-tests tests.cpp) target_link_libraries(${PROJECT_NAME}-tests ${PROJECT_NAME}) +add_executable(${PROJECT_NAME}-c_abi_test + c_abi_test.cpp +) +target_link_libraries(${PROJECT_NAME}-c_abi_test ${PROJECT_NAME}-tests-cabi-gen) +target_include_directories( + ${PROJECT_NAME}-c_abi_test + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + add_executable(${PROJECT_NAME}-tests-unchecked tests.cpp transform_component.cpp) target_link_libraries(${PROJECT_NAME}-tests-unchecked ${PROJECT_NAME}) target_compile_definitions(${PROJECT_NAME}-tests-unchecked PRIVATE TECS_UNCHECKED_MODE) diff --git a/tests/benchmark.cpp b/tests/benchmark.cpp index 6c897af..0bb6e0a 100644 --- a/tests/benchmark.cpp +++ b/tests/benchmark.cpp @@ -1,10 +1,9 @@ #include "test_components.hh" #include "utils.hh" -#include +#include #include #include -#include #include #include #include @@ -20,17 +19,33 @@ using namespace testing; using namespace Tecs; -std::atomic_bool running; -std::atomic_bool success; -static testing::ECS ecs; +#ifdef BENCHMARK_CABI +TECS_IMPLEMENT_C_ABI +#endif -static std::thread::id renderThreadId; -static std::thread::id scriptThreadId; -static std::thread::id transformThreadId; -static std::thread::id scriptTransactionThreadId; +namespace benchmark { +#ifdef BENCHMARK_CABI + using AbiECS = Tecs::abi::ECS; + using Entity = Tecs::abi::Entity; +#endif -Observer> scriptObserver; -std::atomic_size_t scriptUpdateCount; + std::atomic_bool running; + std::atomic_bool success; +#ifdef BENCHMARK_CABI + static AbiECS ecs = AbiECS(); +#else + static testing::ECS ecs; +#endif + + static std::thread::id renderThreadId; + static std::thread::id scriptThreadId; + static std::thread::id transformThreadId; + static std::thread::id scriptTransactionThreadId; + +#ifndef BENCHMARK_CABI + Observer> scriptObserver; +#endif + std::atomic_size_t scriptUpdateCount; #define ENTITY_COUNT 1000000 #define ADD_REMOVE_ITERATIONS 100 @@ -42,501 +57,527 @@ std::atomic_size_t scriptUpdateCount; #define RENDERABLE_DIVISOR 3 #define SCRIPT_DIVISOR 5 -void renderThread() { - renderThreadId = std::this_thread::get_id(); - MultiTimer timer1("RenderThread StartTransaction"); - MultiTimer timer2("RenderThread Run"); - MultiTimer timer3("RenderThread Unlock"); - std::vector bad; - double currentTransformValue = 0; - uint32_t currentScriptValue = 0; - size_t readCount = 0; - size_t badCount = 0; - auto start = std::chrono::high_resolution_clock::now(); - auto lastFrameEnd = start; - while (running) { - { - Timer t(timer1); - auto readLock = ecs.StartTransaction>(); - t = timer2; - - auto &validRenderables = readLock.EntitiesWith(); - auto &validTransforms = readLock.EntitiesWith(); - auto &validEntities = validRenderables.size() > validTransforms.size() ? validTransforms : validRenderables; - auto firstName = &validEntities[0].Get(readLock).name; - Entity firstScriptEntity; - for (auto e : validEntities) { - if (e.Has(readLock)) { - if (!firstScriptEntity && e.Has