From 6ae1030957348f32c2395f8eea89f10ea6b45e9d Mon Sep 17 00:00:00 2001 From: Jacob Wirth Date: Thu, 24 Oct 2024 20:38:28 -0700 Subject: [PATCH 01/39] Remove Permissions... template argument from Template and eliminate virtual class --- inc/Tecs.hh | 4 +- inc/Tecs_entity.hh | 48 +++---- inc/Tecs_lock.hh | 75 ++++++----- inc/Tecs_permissions.hh | 2 +- inc/Tecs_storage.hh | 2 +- inc/Tecs_transaction.hh | 289 ++++++++++++++++++++-------------------- 6 files changed, 214 insertions(+), 206 deletions(-) diff --git a/inc/Tecs.hh b/inc/Tecs.hh index 1a027c7..75eba71 100644 --- a/inc/Tecs.hh +++ b/inc/Tecs.hh @@ -188,10 +188,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_entity.hh b/inc/Tecs_entity.hh index e8dfd80..a216220 100644 --- a/inc/Tecs_entity.hh +++ b/inc/Tecs_entity.hh @@ -74,8 +74,8 @@ namespace Tecs { 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]; @@ -122,11 +122,11 @@ namespace Tecs { "Can't get non-const reference of read only Component."); static_assert(!is_global_component(), "Global components must be accessed through lock.Get()"); - if constexpr (!std::is_const()) lock.base->template SetAccessFlag(true); + if constexpr (!std::is_const()) lock.transaction->template SetAccessFlag(true); #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)); } @@ -141,12 +141,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(true); // Reset value before allowing reading. storage.writeComponents[index] = {}; @@ -162,7 +162,7 @@ namespace Tecs { #endif } - if (lock.instance.template BitsetHas(lock.permissions)) { + if (lock.instance.template BitsetHas(lock.writePermissions)) { return storage.writeComponents[index]; } else { return storage.readComponents[index]; @@ -198,11 +198,11 @@ namespace Tecs { 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()"); - lock.base->template SetAccessFlag(true); + lock.transaction->template SetAccessFlag(true); #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)); } @@ -215,12 +215,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(true); metadata[1 + lock.instance.template GetComponentIndex()] = true; auto &validEntities = lock.instance.template Storage().writeValidEntities; @@ -239,11 +239,11 @@ namespace Tecs { 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()"); - lock.base->template SetAccessFlag(true); + lock.transaction->template SetAccessFlag(true); #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)); } @@ -256,12 +256,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(true); metadata[1 + lock.instance.template GetComponentIndex()] = true; auto &validEntities = lock.instance.template Storage().writeValidEntities; @@ -298,7 +298,7 @@ namespace Tecs { template inline void Destroy(const LockType &lock) const { static_assert(is_add_remove_allowed(), "Entities cannot be destroyed without an AddRemove lock."); - lock.base->writeAccessedFlags[0] = true; + lock.transaction->template SetAccessFlag(true); #ifndef TECS_UNCHECKED_MODE if (index >= lock.instance.metadata.writeComponents.size()) { diff --git a/inc/Tecs_lock.hh b/inc/Tecs_lock.hh index 71a0ee0..c719815 100644 --- a/inc/Tecs_lock.hh +++ b/inc/Tecs_lock.hh @@ -51,24 +51,27 @@ namespace Tecs { using LockType = Lock; ECS &instance; - std::shared_ptr> base; - std::bitset<1 + sizeof...(AllComponentTypes)> permissions; + std::shared_ptr> transaction; + std::bitset<1 + sizeof...(AllComponentTypes)> 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, decltype(writePermissions) 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(); + std::bitset<1 + sizeof...(AllComponentTypes)> 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,7 +94,7 @@ 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) {} inline constexpr ECS &GetInstance() const { return instance; @@ -99,7 +102,7 @@ namespace Tecs { #ifndef TECS_HEADER_ONLY inline size_t GetTransactionId() const { - return base->transactionId; + return transaction->GetTransactionId(); } #endif @@ -114,7 +117,7 @@ namespace Tecs { inline const EntityView EntitiesWith() const { static_assert(!is_global_component(), "Entities can't have global components"); - if (permissions[0]) { + if (writePermissions[0]) { return instance.template Storage().writeValidEntities; } else { return instance.template Storage().readValidEntities; @@ -126,7 +129,7 @@ namespace Tecs { } inline const EntityView Entities() const { - if (permissions[0]) { + if (writePermissions[0]) { return instance.metadata.writeValidEntities; } else { return instance.metadata.readValidEntities; @@ -140,7 +143,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(true); Entity entity; if (instance.freeEntities.empty()) { @@ -179,7 +182,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 (writePermissions[0]) { return instance.template BitsetHas(instance.globalWriteMetadata); } else { return instance.template BitsetHas(instance.globalReadMetadata); @@ -202,19 +205,19 @@ namespace Tecs { "Can't get non-const reference of read only Component."); static_assert(is_global_component(), "Only global components can be accessed without an Entity"); - if (!std::is_const()) base->template SetAccessFlag(true); + if (!std::is_const()) transaction->template SetAccessFlag(true); #ifndef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = writePermissions[0] ? 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 = writePermissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if (!instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(true); metadata[1 + instance.template GetComponentIndex()] = true; storage.writeComponents.resize(1); @@ -226,7 +229,7 @@ namespace Tecs { throw std::runtime_error("Missing global component of type: " + std::string(typeid(CompType).name())); #endif } - if (instance.template BitsetHas(permissions)) { + if (instance.template BitsetHas(writePermissions)) { return storage.writeComponents[0]; } else { return storage.readComponents[0]; @@ -251,18 +254,18 @@ namespace Tecs { 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"); - base->template SetAccessFlag(true); + transaction->template SetAccessFlag(true); #ifndef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = writePermissions[0] ? 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 = writePermissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if (!instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(true); metadata[1 + instance.template GetComponentIndex()] = true; instance.template Storage().writeComponents.resize(1); @@ -279,18 +282,18 @@ namespace Tecs { 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"); - base->template SetAccessFlag(true); + transaction->template SetAccessFlag(true); #ifndef TECS_UNCHECKED_MODE - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = writePermissions[0] ? 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 = writePermissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; #endif if (!instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; + transaction->template SetAccessFlag(true); metadata[1 + instance.template GetComponentIndex()] = true; instance.template Storage().writeComponents.resize(1); @@ -346,18 +349,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(true); + transaction->template SetAccessFlag(true); size_t newSize = instance.template Storage().writeComponents.size() + count; instance.template Storage().writeComponents.resize(newSize); @@ -372,8 +375,8 @@ 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; - base->template SetAccessFlag(true); + transaction->template SetAccessFlag(true); + transaction->template SetAccessFlag(true); metadata[1 + instance.template GetComponentIndex()] = false; auto &compIndex = instance.template Storage(); @@ -390,8 +393,8 @@ namespace Tecs { auto &metadata = instance.globalWriteMetadata; if (instance.template BitsetHas(metadata)) { - base->writeAccessedFlags[0] = true; - base->template SetAccessFlag(true); + transaction->template SetAccessFlag(true); + transaction->template SetAccessFlag(true); metadata[1 + instance.template GetComponentIndex()] = false; instance.template Storage().writeComponents[0] = {}; @@ -466,13 +469,13 @@ namespace Tecs { std::optional> TryLock() const { using DynamicLockType = Lock; if constexpr (Lock::template has_permissions()) { - return DynamicLockType(this->instance, this->base, this->permissions); + return DynamicLockType(this->instance, this->transaction, this->writePermissions); } else { static constexpr auto requestedRead = generateReadBitset(); static constexpr 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->writePermissions); } return {}; } diff --git a/inc/Tecs_permissions.hh b/inc/Tecs_permissions.hh index decc83d..b8e1c4e 100644 --- a/inc/Tecs_permissions.hh +++ b/inc/Tecs_permissions.hh @@ -12,7 +12,7 @@ namespace Tecs { class Lock {}; template class DynamicLock {}; - template + template class Transaction {}; /** diff --git a/inc/Tecs_storage.hh b/inc/Tecs_storage.hh index 98fbc80..ac1cf50 100644 --- a/inc/Tecs_storage.hh +++ b/inc/Tecs_storage.hh @@ -349,7 +349,7 @@ namespace Tecs { template friend class Lock; - template + template friend class Transaction; friend struct Entity; }; diff --git a/inc/Tecs_transaction.hh b/inc/Tecs_transaction.hh index b96fb2f..58b16bf 100644 --- a/inc/Tecs_transaction.hh +++ b/inc/Tecs_transaction.hh @@ -39,56 +39,13 @@ 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() { -#ifndef TECS_HEADER_ONLY - auto start = activeTransactions.begin(); - activeTransactionsCount = std::remove(start, start + activeTransactionsCount, instance.ecsId) - start; -#endif - } - - protected: - ECSType &instance; -#ifndef TECS_HEADER_ONLY - size_t transactionId; -#endif - - std::bitset<1 + sizeof...(AllComponentTypes)> writeAccessedFlags; - - template - inline void SetAccessFlag(bool value) { - writeAccessedFlags[1 + instance.template GetComponentIndex()] = value; - } - - template - friend class Lock; - friend struct Entity; - }; - - template - class Transaction, Permissions...> : public BaseTransaction { private: - using LockType = Lock, Permissions...>; + // using FlatPermissions = typename FlattenPermissions::type; using EntityMetadata = typename ECS::EntityMetadata; - using FlatPermissions = typename FlattenPermissions::type; #ifdef TECS_ENABLE_TRACY static inline const auto tracyCtx = []() -> const tracy::SourceLocationData * { @@ -106,47 +63,101 @@ namespace Tecs { #endif #endif + ECSType &instance; +#ifndef TECS_HEADER_ONLY + size_t transactionId; +#endif + + const PermissionBitset readPermissions; + const PermissionBitset writePermissions; + PermissionBitset writeAccessedFlags; + public: - inline Transaction(ECS &instance) : BaseTransaction(instance) { + template + inline bool IsReadAllowed() const { + return readPermissions[1 + instance.template GetComponentIndex()]; + } + + template + inline bool IsWriteAllowed() const { + return writePermissions[1 + instance.template GetComponentIndex()]; + } + + inline bool IsAddRemoveAllowed() const { + return writePermissions[0]; + } + + template + inline void SetAccessFlag(bool value) { + writeAccessedFlags[1 + instance.template GetComponentIndex()] = value; + } + template<> + inline void SetAccessFlag(bool value) { + writeAccessedFlags[0] = value; + } + +#ifndef TECS_HEADER_ONLY + inline size_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 + ZoneNamedN(tracyScope, "StartTransaction", true); +#endif #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. @@ -174,42 +185,42 @@ namespace Tecs { } } - if (is_add_remove_allowed()) { + if (IsAddRemoveAllowed()) { // Init observer event queues std::apply( [](auto &...args) { (args.Init(), ...); }, - this->instance.eventLists); + instance.eventLists); } #ifdef TECS_ENABLE_PERFORMANCE_TRACING TECS_EXTERNAL_TRACE_TRANSACTION_STARTED(FlatPermissions::Name()); #endif } + // Delete copy constructor + Transaction(const Transaction &) = delete; - inline ~Transaction() { + ~Transaction() { #ifdef TECS_ENABLE_PERFORMANCE_TRACING TECS_EXTERNAL_TRACE_TRANSACTION_ENDING(FlatPermissions::Name()); #endif #ifdef TECS_ENABLE_TRACY ZoneNamedN(tracyTxScope, "EndTransaction", true); #endif - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) { - PreCommitAddRemoveMetadata(); - (PreCommitAddRemove(), ...); - } + if (IsAddRemoveAllowed() && writeAccessedFlags[0]) { + PreCommitAddRemoveMetadata(); + (PreCommitAddRemove(), ...); } ( // 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(); } }(), ...); @@ -218,15 +229,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(); } }(), ...); @@ -235,33 +245,29 @@ namespace Tecs { #if defined(TECS_ENABLE_TRACY) && defined(TECS_TRACY_INCLUDE_DETAILED_COMMIT) ZoneNamedN(tracyCommitScope2, "Commit", true); #endif - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) { - // Commit observers - std::apply( - [](auto &...args) { - (args.Commit(), ...); - }, - this->instance.eventLists); - - 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]) { + // Commit observers + std::apply( + [](auto &...args) { + (args.Commit(), ...); + }, + instance.eventLists); + + 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(); } @@ -271,7 +277,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, @@ -279,12 +285,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]) { storage.writeComponents = storage.readComponents; storage.writeValidEntities = storage.readValidEntities; } else { @@ -302,21 +308,23 @@ 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; } - } - if constexpr (is_add_remove_allowed()) { - 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 } @@ -325,30 +333,29 @@ namespace Tecs { inline void PreCommitAddRemoveMetadata() const { // 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) { - auto &observerList = this->instance.template Observers(); + auto &observerList = instance.template Observers(); if (oldMetadata[0]) { observerList.writeQueue->emplace_back(EventType::REMOVED, Entity(index, oldMetadata.generation)); @@ -363,46 +370,46 @@ namespace Tecs { template inline void PreCommitAddRemove() const { if constexpr (is_global_component()) { - const auto &oldMetadata = this->instance.globalReadMetadata; - const auto &newMetadata = this->instance.globalWriteMetadata; - if (this->instance.template BitsetHas(newMetadata)) { - if (!this->instance.template BitsetHas(oldMetadata)) { - auto &observerList = this->instance.template Observers>(); + const auto &oldMetadata = instance.globalReadMetadata; + const auto &newMetadata = instance.globalWriteMetadata; + if (instance.template BitsetHas(newMetadata)) { + if (!instance.template BitsetHas(oldMetadata)) { + auto &observerList = instance.template Observers>(); observerList.writeQueue->emplace_back(EventType::ADDED, Entity(), - this->instance.template Storage().writeComponents[0]); + instance.template Storage().writeComponents[0]); } - } else if (this->instance.template BitsetHas(oldMetadata)) { - auto &observerList = this->instance.template Observers>(); + } else if (instance.template BitsetHas(oldMetadata)) { + auto &observerList = instance.template Observers>(); observerList.writeQueue->emplace_back(EventType::REMOVED, Entity(), - this->instance.template Storage().readComponents[0]); + instance.template Storage().readComponents[0]); } } else { - auto &storage = this->instance.template Storage(); + auto &storage = instance.template Storage(); // Rebuild writeValidEntities and validEntityIndexes with the new entity set. storage.writeValidEntities.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.template BitsetHas(newMetadata)) { + if (newMetadata[0] && instance.template BitsetHas(newMetadata)) { storage.validEntityIndexes[index] = storage.writeValidEntities.size(); storage.writeValidEntities.emplace_back(index, newMetadata.generation); } // Compare new and old metadata to notify observers - bool newExists = this->instance.template BitsetHas(newMetadata); - bool oldExists = this->instance.template BitsetHas(oldMetadata); + bool newExists = instance.template BitsetHas(newMetadata); + bool oldExists = instance.template BitsetHas(oldMetadata); if (newExists != oldExists || newMetadata.generation != oldMetadata.generation) { - auto &observerList = this->instance.template Observers>(); + auto &observerList = instance.template Observers>(); if (oldExists) { observerList.writeQueue->emplace_back(EventType::REMOVED, Entity(index, oldMetadata.generation), From d02d255dbb16e5a063ec45ccceef6bd684b856a0 Mon Sep 17 00:00:00 2001 From: Jacob Wirth Date: Sun, 20 Oct 2024 00:14:13 -0700 Subject: [PATCH 02/39] Add c abi headers and code-gen implementation (WIP) --- .vscode/launch.json | 11 ++ inc/c_abi/Tecs_entity.h | 28 +++ inc/c_abi/Tecs_lock.h | 39 ++++ src/c_abi/impl_gen_common.hh | 60 ++++++ src/c_abi/impl_gen_entity.cc | 264 +++++++++++++++++++++++++++ src/c_abi/impl_gen_lock.cc | 343 +++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 47 +++++ tests/c_abi_test.cpp | 178 ++++++++++++++++++ 8 files changed, 970 insertions(+) create mode 100644 inc/c_abi/Tecs_entity.h create mode 100644 inc/c_abi/Tecs_lock.h create mode 100644 src/c_abi/impl_gen_common.hh create mode 100644 src/c_abi/impl_gen_entity.cc create mode 100644 src/c_abi/impl_gen_lock.cc create mode 100644 tests/c_abi_test.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 815a672..9350c9c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,6 +34,17 @@ "environment": [], "console": "integratedTerminal" }, + { + "name": "Launch C-ABI Tests (Windows)", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/build/tests/Tecs-c_abi_test.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build/tests/", + "environment": [], + "console": "integratedTerminal" + }, { "name": "Launch Tests (Linux)", "type": "cppdbg", diff --git a/inc/c_abi/Tecs_entity.h b/inc/c_abi/Tecs_entity.h new file mode 100644 index 0000000..8ba3238 --- /dev/null +++ b/inc/c_abi/Tecs_entity.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#else +typedef uint8_t bool; +#endif + +#include "Tecs_lock.h" + +#include + +typedef uint64_t TecsEntity; + +bool Tecs_entity_exists(TecsLock *dynLockPtr, TecsEntity entity); +bool Tecs_entity_existed(TecsLock *dynLockPtr, TecsEntity entity); +bool Tecs_entity_has(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex); +bool Tecs_entity_had(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex); +const void *Tecs_entity_const_get(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex); +void *Tecs_entity_get(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex); +const void *Tecs_entity_get_previous(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex); +void *Tecs_entity_set(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex, const void *value); +void Tecs_entity_unset(TecsLock *dynLockPtr, TecsEntity entity, size_t componentIndex); +void Tecs_entity_destroy(TecsLock *dynLockPtr, TecsEntity entity); + +#ifdef __cplusplus +} +#endif diff --git a/inc/c_abi/Tecs_lock.h b/inc/c_abi/Tecs_lock.h new file mode 100644 index 0000000..b5b86d5 --- /dev/null +++ b/inc/c_abi/Tecs_lock.h @@ -0,0 +1,39 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#else +typedef uint8_t bool; +#endif + +#include + +typedef uint64_t TecsEntity; + +typedef void TecsLock; + +size_t Tecs_lock_get_transaction_id(TecsLock *dynLockPtr); + +size_t Tecs_previous_entities_with(TecsLock *dynLockPtr, size_t componentIndex, const TecsEntity **output); +size_t Tecs_entities_with(TecsLock *dynLockPtr, size_t componentIndex, const TecsEntity **output); +size_t Tecs_previous_entities(TecsLock *dynLockPtr, const TecsEntity **output); +size_t Tecs_entities(TecsLock *dynLockPtr, const TecsEntity **output); +TecsEntity Tecs_new_entity(TecsLock *dynLockPtr); +bool Tecs_has(TecsLock *dynLockPtr, size_t componentIndex); +bool Tecs_had(TecsLock *dynLockPtr, size_t componentIndex); +const void *Tecs_const_get(TecsLock *dynLockPtr, size_t componentIndex); +void *Tecs_get(TecsLock *dynLockPtr, size_t componentIndex); +const void *Tecs_get_previous(TecsLock *dynLockPtr, size_t componentIndex); +void *Tecs_set(TecsLock *dynLockPtr, size_t componentIndex, const void *value); +void Tecs_unset(TecsLock *dynLockPtr, size_t componentIndex); + +// Observer Watch(); +// void StopWatching(Observer observer); + +// New lock must be released +TecsLock *Tecs_lock_read_only(TecsLock *dynLockPtr); +void Tecs_lock_release(TecsLock *dynLockPtr); + +#ifdef __cplusplus +} +#endif diff --git a/src/c_abi/impl_gen_common.hh b/src/c_abi/impl_gen_common.hh new file mode 100644 index 0000000..5f5dc1c --- /dev/null +++ b/src/c_abi/impl_gen_common.hh @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define STRING(s) #s +#define STRINGIFY(s) STRING(s) + +#ifdef 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 + +template +auto EmbedTypeIntoSignature() { + return std::string_view{std::source_location::current().function_name()}; +} + +template +auto TypeToString() { + auto dummyInt = EmbedTypeIntoSignature(); + auto intStart = dummyInt.find("int"); + auto tailLength = dummyInt.size() - intStart - std::string("int").length(); + + auto typeStart = intStart; + auto embeddingSignature = EmbedTypeIntoSignature(); + auto enumStart = embeddingSignature.find("enum ", intStart); + if (enumStart == intStart) typeStart += std::string("enum ").length(); + auto classStart = embeddingSignature.find("class ", intStart); + if (classStart == intStart) typeStart += std::string("class ").length(); + auto structStart = embeddingSignature.find("struct ", intStart); + if (structStart == intStart) typeStart += std::string("struct ").length(); + + auto typeLength = embeddingSignature.size() - typeStart - tailLength; + return embeddingSignature.substr(typeStart, typeLength); +} + +template +struct CodeGenerator; + +template typename ECSType, typename... AllComponentTypes> +struct CodeGenerator> { + static constexpr std::array GetComponentNames() { + return { + TypeToString()..., + }; + } + + static constexpr std::array GetComponentGlobalList() { + return { + Tecs::is_global_component()..., + }; + } +}; diff --git a/src/c_abi/impl_gen_entity.cc b/src/c_abi/impl_gen_entity.cc new file mode 100644 index 0000000..b30a45f --- /dev/null +++ b/src/c_abi/impl_gen_entity.cc @@ -0,0 +1,264 @@ +#include "impl_gen_common.hh" + +template +void generateEntityCC(T &out) { + auto names = CodeGenerator::GetComponentNames(); + auto globalList = CodeGenerator::GetComponentGlobalList(); +#ifdef TECS_C_ABI_ECS_INCLUDE + out << "#include " STRINGIFY(TECS_C_ABI_ECS_INCLUDE) << std::endl; +#endif + out << R"RAWSTR( +#include +#include + +)RAWSTR"; + out << "using ECS = " << TypeToString(); + out << R"RAWSTR(; +using DynamicLock = Tecs::DynamicLock; + +extern "C" { + +bool Tecs_entity_exists(TecsLock *dynLockPtr, TecsEntity entity) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return Tecs::Entity(entity).Exists(*dynLock); +} + +bool Tecs_entity_existed(TecsLock *dynLockPtr, TecsEntity entity) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return Tecs::Entity(entity).Existed(*dynLock); +} + +bool Tecs_entity_has(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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 << "Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +bool Tecs_entity_had(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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 << "Component index out of range: " << componentIndex << std::endl; + return false; + } +} + +const void *Tecs_entity_const_get(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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 &Tecs::Entity(entity).Get(*lock);" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +void *Tecs_entity_get(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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 << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +const void *Tecs_entity_get_previous(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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 &Tecs::Entity(entity).GetPrevious<" << names[i] << ">(*lock);" << std::endl; + } + } + out << " } else {"; + out << R"RAWSTR( + std::cerr << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +void *Tecs_entity_set(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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).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 << "Component index out of range: " << componentIndex << std::endl; + return nullptr; + } +} + +void Tecs_entity_unset(TecsLock *dynLockPtr, TecsEntity entity, size_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 << \"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 << "Component index out of range: " << componentIndex << std::endl; + } +} + +void Tecs_entity_destroy(TecsLock *dynLockPtr, TecsEntity 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"; +} + +int main(int argc, char **argv) { + if (argc > 1) { + auto out = std::ofstream(argv[1], std::ios::trunc); + generateEntityCC(out); + } else { + generateEntityCC(std::cout); + } +} diff --git a/src/c_abi/impl_gen_lock.cc b/src/c_abi/impl_gen_lock.cc new file mode 100644 index 0000000..572fee0 --- /dev/null +++ b/src/c_abi/impl_gen_lock.cc @@ -0,0 +1,343 @@ +#include "impl_gen_common.hh" + +template +void generateLockCC(T &out) { + auto names = CodeGenerator::GetComponentNames(); + auto globalList = CodeGenerator::GetComponentGlobalList(); +#ifdef TECS_C_ABI_ECS_INCLUDE + out << "#include " STRINGIFY(TECS_C_ABI_ECS_INCLUDE) << std::endl; +#endif + out << R"RAWSTR( +#include +#include + +)RAWSTR"; + out << "using ECS = " << TypeToString(); + out << R"RAWSTR(; +using DynamicLock = Tecs::DynamicLock; + +extern "C" { + +size_t Tecs_lock_get_transaction_id(TecsLock *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return dynLock->GetTransactionId(); +} + +size_t Tecs_previous_entities_with(TecsLock *dynLockPtr, size_t componentIndex, const TecsEntity **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 = reinterpret_cast(&*view.begin()); + return view.size(); +} + +size_t Tecs_entities_with(TecsLock *dynLockPtr, size_t componentIndex, const TecsEntity **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 = reinterpret_cast(&*view.begin()); + return view.size(); +} + +size_t Tecs_previous_entities(TecsLock *dynLockPtr, const TecsEntity **output) { + DynamicLock *dynLock = static_cast(dynLockPtr); + auto view = dynLock->PreviousEntities(); + *output = reinterpret_cast(&*view.begin()); + return view.size(); +} + +size_t Tecs_entities(TecsLock *dynLockPtr, const TecsEntity **output) { + DynamicLock *dynLock = static_cast(dynLockPtr); + auto view = dynLock->Entities(); + *output = reinterpret_cast(&*view.begin()); + return view.size(); +} + +TecsEntity Tecs_new_entity(TecsLock *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 (size_t)lock->NewEntity(); +} + +bool Tecs_has(TecsLock *dynLockPtr, size_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; + } +} + +bool Tecs_had(TecsLock *dynLockPtr, size_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; + } +} + +const void *Tecs_const_get(TecsLock *dynLockPtr, size_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; + } +} + +void *Tecs_get(TecsLock *dynLockPtr, size_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; + } +} + +const void *Tecs_get_previous(TecsLock *dynLockPtr, size_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; + } +} + +void *Tecs_set(TecsLock *dynLockPtr, size_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; + } +} + +void Tecs_unset(TecsLock *dynLockPtr, size_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; + } +} + +// Observer Watch(); +// void StopWatching(Observer observer); + +TecsLock *Tecs_lock_read_only(TecsLock *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + return new DynamicLock(dynLock->ReadOnlySubset()); +} + +void Tecs_lock_release(TecsLock *dynLockPtr) { + DynamicLock *dynLock = static_cast(dynLockPtr); + delete dynLock; +} + +} // extern "C" +)RAWSTR"; +} + +int main(int argc, char **argv) { + if (argc > 1) { + auto out = std::ofstream(argv[1], std::ios::trunc); + generateLockCC(out); + } else { + generateLockCC(std::cout); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 998c436..048b40b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,53 @@ endif() add_executable(${PROJECT_NAME}-tests tests.cpp transform_component.cpp) target_link_libraries(${PROJECT_NAME}-tests ${PROJECT_NAME}) +add_executable(Tecs-gen-entity ${CMAKE_CURRENT_SOURCE_DIR}/../src/c_abi/impl_gen_entity.cc) +add_executable(Tecs-gen-lock ${CMAKE_CURRENT_SOURCE_DIR}/../src/c_abi/impl_gen_lock.cc) +target_include_directories( + Tecs-gen-entity + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../inc + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_include_directories( + Tecs-gen-lock + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../inc + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_compile_definitions(Tecs-gen-entity PRIVATE + TECS_C_ABI_ECS_INCLUDE="test_components.hh" + TECS_C_ABI_ECS_NAME=testing::ECS +) +target_compile_definitions(Tecs-gen-lock PRIVATE + TECS_C_ABI_ECS_INCLUDE="test_components.hh" + TECS_C_ABI_ECS_NAME=testing::ECS +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Tecs_entity_gen.cc + COMMAND Tecs-gen-entity ${CMAKE_CURRENT_BINARY_DIR}/Tecs_entity_gen.cc + DEPENDS Tecs-gen-entity +) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Tecs_lock_gen.cc + COMMAND Tecs-gen-lock ${CMAKE_CURRENT_BINARY_DIR}/Tecs_lock_gen.cc + DEPENDS Tecs-gen-lock +) + +add_executable(${PROJECT_NAME}-c_abi_test + c_abi_test.cpp + transform_component.cpp + ${CMAKE_CURRENT_BINARY_DIR}/Tecs_entity_gen.cc + ${CMAKE_CURRENT_BINARY_DIR}/Tecs_lock_gen.cc +) +target_link_libraries(${PROJECT_NAME}-c_abi_test ${PROJECT_NAME}) +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/c_abi_test.cpp b/tests/c_abi_test.cpp new file mode 100644 index 0000000..a31ade4 --- /dev/null +++ b/tests/c_abi_test.cpp @@ -0,0 +1,178 @@ +#include "test_components.hh" +#include "test_ecs.hh" +#include "tests.hh" +#include "utils.hh" + +#include +#include +#include +#include + +using namespace testing; + +static ECS ecs; + +#define ENTITY_COUNT 10000 + +int main(int /* argc */, char ** /* argv */) { + std::cout << "Running with " << ENTITY_COUNT << " entities and " << ecs.GetComponentCount() << " component types" + << std::endl; + std::cout << ecs.GetBytesPerEntity() << " bytes per entity * N = " << (ecs.GetBytesPerEntity() * ENTITY_COUNT) + << " bytes total" << std::endl; + std::cout << "Using C ABI" << std::endl; + + Assert(Tecs::nextTransactionId == 0, "Expected next transaction id to be 0"); + + Tecs::Observer entityObserver; + Tecs::Observer> transformObserver; + Tecs::Observer> globalCompObserver; + { + Timer t("Test creating new observers"); + auto writeLock = ecs.StartTransaction(); + Tecs::DynamicLock dynLock = writeLock; + entityObserver = writeLock.Watch(); + transformObserver = writeLock.Watch>(); + globalCompObserver = writeLock.Watch>(); + + Assert(Tecs_lock_get_transaction_id(&dynLock) == 1, "Expected transaction id to be 1"); + } + Assert(Tecs::nextTransactionId == 1, "Expected next transaction id to be 1"); + bool globalComponentInitialized = false; + { + Timer t("Test initializing global components"); + auto writeLock = ecs.StartTransaction(); + Tecs::DynamicLock dynLock = writeLock; + Assert(!Tecs_has(&dynLock, ECS::GetComponentIndex()), + "ECS must start with no global component"); + GlobalComponent tmp(0); + GlobalComponent *gc = + static_cast(Tecs_set(&dynLock, ECS::GetComponentIndex(), &tmp)); + Assert(gc, "ECS should have returned global component"); + Assert(Tecs_has(&dynLock, ECS::GetComponentIndex()), "ECS should have a global component"); + Assert(gc->globalCounter == 0, "Global counter should be initialized to zero"); + gc->globalCounter++; + + globalComponentInitialized = true; + gc->test = std::shared_ptr(&globalComponentInitialized, [](bool *b) { + *b = false; + }); + + Assert(!Tecs_had(&dynLock, ECS::GetComponentIndex()), + "ECS shouldn't have a global component previously"); + const GlobalComponent *gcconst = + static_cast(Tecs_const_get(&dynLock, ECS::GetComponentIndex())); + Assert(gcconst->globalCounter == 1, "Expected to be able to read const global counter"); + + GlobalComponent *gc2 = + static_cast(Tecs_get(&dynLock, ECS::GetComponentIndex())); + Assert(gc2->globalCounter == 1, "Global counter should be read back as 1"); + Assert(globalComponentInitialized, "Global component should be initialized"); + + Assert(Tecs_lock_get_transaction_id(&dynLock) == 2, "Expected transaction id to be 2"); + } + { + Timer t("Test update global counter"); + auto writeLock = ecs.StartTransaction>(); + Tecs::DynamicLock dynLock = writeLock; + Assert(Tecs_has(&dynLock, ECS::GetComponentIndex()), "ECS should have a global component"); + + GlobalComponent *gc = + static_cast(Tecs_get(&dynLock, ECS::GetComponentIndex())); + Assert(gc->globalCounter == 1, "Global counter should be read back as 1"); + gc->globalCounter++; + + Assert(Tecs_had(&dynLock, ECS::GetComponentIndex()), + "ECS shouldn have a global component previously"); + Assert( + static_cast(Tecs_get_previous(&dynLock, ECS::GetComponentIndex())) + ->globalCounter == 1, + "Expected previous counter to be 1"); + Assert(static_cast(Tecs_get(&dynLock, ECS::GetComponentIndex())) + ->globalCounter == 2, + "Expected current counter to be 2"); + Assert(static_cast(Tecs_const_get(&dynLock, ECS::GetComponentIndex())) + ->globalCounter == 2, + "Expected const current counter to be 2"); + + Assert(globalComponentInitialized, "Global component should be initialized"); + } + { + Timer t("Test read global counter"); + auto readLock = ecs.StartTransaction>(); + Tecs::DynamicLock dynLock = readLock; + Assert(Tecs_has(&dynLock, ECS::GetComponentIndex()), "ECS should have a global component"); + + const GlobalComponent *gc = + static_cast(Tecs_const_get(&dynLock, ECS::GetComponentIndex())); + Assert(gc->globalCounter == 2, "Global counter should be read back as 2"); + } + { + Timer t("Test remove global component"); + auto writeLock = ecs.StartTransaction(); + Tecs::DynamicLock dynLock = writeLock; + Assert(Tecs_has(&dynLock, ECS::GetComponentIndex()), "ECS should have a global component"); + + GlobalComponent *gc = + static_cast(Tecs_get(&dynLock, ECS::GetComponentIndex())); + Assert(gc->globalCounter == 2, "Global counter should be read back as 2"); + + Tecs_unset(&dynLock, ECS::GetComponentIndex()); + Assert(!Tecs_has(&dynLock, ECS::GetComponentIndex()), "Global component should be removed"); + Assert(Tecs_had(&dynLock, ECS::GetComponentIndex()), "ECS should still know previous state"); + Assert(globalComponentInitialized, "Global component should still be initialized (kept by read pointer)"); + } + Assert(globalComponentInitialized, "Global component should still be initialized (kept by observer)"); + { + Timer t("Test add remove global component in single transaction"); + auto writeLock = ecs.StartTransaction(); + Tecs::DynamicLock dynLock = writeLock; + Assert(!Tecs_has(&dynLock, ECS::GetComponentIndex()), "Global component should be removed"); + + GlobalComponent *gc = + static_cast(Tecs_get(&dynLock, ECS::GetComponentIndex())); + Assert(Tecs_has(&dynLock, ECS::GetComponentIndex()), + "Get call should have initialized global component"); + Assert(gc->globalCounter == 10, "Global counter should be default initialized to 10"); + + bool compInitialized = true; + gc->test = std::shared_ptr(&compInitialized, [](bool *b) { + *b = false; + }); + + // Try removing the component in the same transaction it was created + Tecs_unset(&dynLock, ECS::GetComponentIndex()); + Assert(!Tecs_has(&dynLock, ECS::GetComponentIndex()), "Global component should be removed"); + + Assert(!compInitialized, "Global component should be deconstructed immediately"); + } + { + Timer t("Test simple add entity"); + auto writeLock = ecs.StartTransaction(); + Tecs::DynamicLock dynLock = writeLock; + for (size_t i = 0; i < 100; i++) { + Tecs::Entity e = Tecs_new_entity(&dynLock); + Assert(e.index == i, + "Expected Nth entity index to be " + std::to_string(ENTITY_COUNT + i) + ", was " + std::to_string(e)); + Assert(Tecs::GenerationWithoutIdentifier(e.generation) == 1, + "Expected Nth entity generation to be 1, was " + std::to_string(e)); + Assert(Tecs::IdentifierFromGeneration(e.generation) == 1, + "Expected Nth entity ecsId to be 1, was " + std::to_string(e)); + + Transform tmpTransform(0.0, 0.0, 0.0); + Tecs_entity_set(&dynLock, (TecsEntity)e, ECS::GetComponentIndex(), &tmpTransform); + Script tmpScript(std::initializer_list({1, 2, 3, 4})); + Tecs_entity_set(&dynLock, (TecsEntity)e, ECS::GetComponentIndex