diff --git a/inc/Tecs.hh b/inc/Tecs.hh index 1a027c7..f19c1c0 100644 --- a/inc/Tecs.hh +++ b/inc/Tecs.hh @@ -189,9 +189,9 @@ namespace Tecs { template friend class Lock; template + friend class EntityLock; + 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 e257ab4..4968674 100644 --- a/inc/Tecs_entity.hh +++ b/inc/Tecs_entity.hh @@ -24,7 +24,10 @@ static_assert(sizeof(TECS_ENTITY_GENERATION_TYPE) > sizeof(TECS_ENTITY_ECS_IDENT namespace Tecs { struct Entity; -}; + + template + class EntityLock; +}; // namespace Tecs namespace std { inline string to_string(const Tecs::Entity &ent); @@ -51,6 +54,8 @@ namespace Tecs { return (TECS_ENTITY_ECS_IDENTIFIER_TYPE)(generation >> generationBits); } + using namespace std::string_literals; + 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 @@ -68,14 +73,23 @@ namespace Tecs { public: template inline bool Exists(LockType &lock) const { - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.readAliasesWriteStorage[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; } + template + inline bool Exists(EntityLock &lock) const { + if (*this == lock.entity) { + return Exists(lock.lock); + } else { + return Existed(lock.lock); + } + } + template inline bool Existed(LockType &lock) const { if (index >= lock.instance.metadata.readComponents.size()) return false; @@ -84,11 +98,16 @@ namespace Tecs { return metadata[0] && metadata.generation == generation; } + template + inline bool Existed(EntityLock &lock) const { + return Existed(lock.lock); + } + template inline bool Has(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.readAliasesWriteStorage[0] ? lock.instance.metadata.writeComponents + : lock.instance.metadata.readComponents; if (index >= metadataList.size()) return false; auto &metadata = metadataList[index]; @@ -96,6 +115,16 @@ namespace Tecs { lock.instance.template BitsetHas(metadata); } + template + inline bool Has(EntityLock &lock) const { + static_assert(!contains_global_components(), "Entities cannot have global components"); + if (*this == lock.entity) { + return Has(lock.lock); + } else { + return Had(lock.lock); + } + } + template inline bool Had(LockType &lock) const { static_assert(!contains_global_components(), "Entities cannot have global components"); @@ -106,6 +135,12 @@ namespace Tecs { lock.instance.template BitsetHas(metadata); } + template + inline bool Had(EntityLock &lock) const { + static_assert(!contains_global_components(), "Entities cannot have global components"); + return Had(lock.lock); + } + template, LockType>::value, T, const T>> @@ -118,8 +153,8 @@ namespace Tecs { if constexpr (!std::is_const()) lock.base->template SetAccessFlag(true); - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.readAliasesWriteStorage[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)); } @@ -140,18 +175,31 @@ namespace Tecs { lock.instance.template Storage().validEntityIndexes[index] = validEntities.size(); validEntities.emplace_back(*this); } else { - throw std::runtime_error( - "Entity does not have a component of type: " + std::string(typeid(CompType).name())); + throw std::runtime_error("Entity does not have a component of type: "s + typeid(CompType).name()); } } - if (lock.instance.template BitsetHas(lock.permissions)) { + if (lock.instance.template BitsetHas(lock.readAliasesWriteStorage)) { return lock.instance.template Storage().writeComponents[index]; } else { return lock.instance.template Storage().readComponents[index]; } } + // EntityLock writes must be done through EntityLock::Get(). + template + inline const std::remove_cv_t &Get(EntityLock &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.Get()"); + if (*this == lock.entity) { + return Get(lock.lock); + } else { + return GetPrevious(lock.lock); + } + } + template inline const T &GetPrevious(LockType &lock) const { using CompType = std::remove_cv_t; @@ -169,20 +217,29 @@ namespace Tecs { } if (!lock.instance.template BitsetHas(metadata)) { - throw std::runtime_error( - "Entity does not have a component of type: " + std::string(typeid(CompType).name())); + throw std::runtime_error("Entity does not have a component of type: "s + typeid(CompType).name()); } return lock.instance.template Storage().readComponents[index]; } + template + inline const T &GetPrevious(EntityLock &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()"); + return GetPrevious(lock.lock); + } + template inline T &Set(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); - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.readAliasesWriteStorage[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)); } @@ -201,21 +258,32 @@ namespace Tecs { lock.instance.template Storage().validEntityIndexes[index] = validEntities.size(); validEntities.emplace_back(*this); } else { - throw std::runtime_error( - "Entity does not have a component of type: " + std::string(typeid(T).name())); + throw std::runtime_error("Entity does not have a component of type: "s + typeid(T).name()); } } return lock.instance.template Storage().writeComponents[index] = value; } + template + inline T &Set(EntityLock &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()"); + if (*this == lock.entity) { + return Set(lock.lock, value); + } else { + throw std::runtime_error("Entity is not locked for writing: " + std::to_string(*this) + + " lock is for " + std::to_string(lock.entity)); + } + } + template inline T &Set(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); - auto &metadataList = - lock.permissions[0] ? lock.instance.metadata.writeComponents : lock.instance.metadata.readComponents; + auto &metadataList = lock.readAliasesWriteStorage[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)); } @@ -234,13 +302,24 @@ namespace Tecs { lock.instance.template Storage().validEntityIndexes[index] = validEntities.size(); validEntities.emplace_back(*this); } else { - throw std::runtime_error( - "Entity does not have a component of type: " + std::string(typeid(T).name())); + throw std::runtime_error("Entity does not have a component of type: "s + typeid(T).name()); } } return lock.instance.template Storage().writeComponents[index] = T(std::forward(args)...); } + template + inline T &Set(EntityLock &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()"); + if (*this == lock.entity) { + return Set(lock.lock, std::forward(args)...); + } else { + throw std::runtime_error("Entity is not locked for writing: " + std::to_string(*this) + + " lock is for " + std::to_string(lock.entity)); + } + } + template inline void Unset(LockType &lock) const { static_assert(is_add_remove_allowed(), "Components cannot be removed without an AddRemove lock."); diff --git a/inc/Tecs_lock.hh b/inc/Tecs_lock.hh index 3708bb0..6165274 100644 --- a/inc/Tecs_lock.hh +++ b/inc/Tecs_lock.hh @@ -23,7 +23,7 @@ static_assert(TECS_ENTITY_ALLOCATION_BATCH_SIZE > 0, "At least 1 entity needs to namespace Tecs { template - class DynamicLock; + class EntityLock; /** * Lock is a reference to lock permissions held by an active Transaction. @@ -49,32 +49,36 @@ namespace Tecs { private: using ECS = ECSType; using LockType = Lock; + using FlatPermissions = typename FlattenPermissions::type; ECS &instance; - std::shared_ptr> base; - std::bitset<1 + sizeof...(AllComponentTypes)> permissions; - - // Private constructor for DynamicLock to Lock conversion - template - inline Lock(ECS &instance, decltype(base) base, decltype(permissions) permissions) - : instance(instance), base(base), permissions(permissions) {} + std::shared_ptr> base; + std::bitset<1 + sizeof...(AllComponentTypes)> readAliasesWriteStorage; public: // Start a new transaction inline Lock(ECS &instance) : instance(instance) { - base = std::make_shared>(instance); - permissions[0] = is_add_remove_allowed(); - // clang-format off - (( - permissions[1 + instance.template GetComponentIndex()] = is_write_allowed() - ), ...); - // clang-format on + base = std::make_shared>(instance, *this); +#ifdef TECS_ENABLE_TRACY + static const tracy::SourceLocationData srcloc{"TecsTransaction", + FlatPermissions::Name(), + __FILE__, + __LINE__, + 0}; + #if defined(TRACY_HAS_CALLSTACK) && defined(TRACY_CALLSTACK) + base->tracyZone.emplace(&srcloc, TRACY_CALLSTACK, true); + #else + base->tracyZone.emplace(&srcloc, true); + #endif +#endif + readAliasesWriteStorage = base->writePermissions; + base->template AcquireLockReference(); } // Returns true if this lock type can be constructed from a lock with the specified source permissions - template + template static constexpr bool is_lock_subset() { - using SourceLockType = Lock; + using SourceLockType = Lock; if constexpr (is_add_remove_allowed() && !is_add_remove_allowed()) { return false; } else { @@ -88,10 +92,50 @@ namespace Tecs { return Lock::template is_lock_subset(); } - // Reference an existing transaction - template(), int> = 0> - inline Lock(const Lock &source) - : instance(source.instance), base(source.base), permissions(source.permissions) {} + // Reference an identical lock + inline Lock(const LockType &source) + : instance(source.instance), base(source.base), readAliasesWriteStorage(source.readAliasesWriteStorage) { + base->template AcquireLockReference(); + } + + // Reference a subset of an existing transaction + template(), int> = 0> + inline Lock(const Lock &source) : instance(source.instance), base(source.base) { + if constexpr (is_add_remove_allowed()) { + readAliasesWriteStorage[0] = true; + } else { + readAliasesWriteStorage[0] = source.readAliasesWriteStorage[0]; + } + ( // For each AllComponentTypes, copy the source permissions + [&] { + constexpr auto compIndex = 1 + ECS::template GetComponentIndex(); + if constexpr (is_write_allowed()) { + readAliasesWriteStorage[compIndex] = true; + } else if constexpr (is_write_optional()) { + readAliasesWriteStorage[compIndex] = source.readAliasesWriteStorage[compIndex]; + } else if constexpr (is_read_allowed()) { + readAliasesWriteStorage[compIndex] = source.readAliasesWriteStorage[compIndex]; + } else if constexpr (is_read_optional()) { + readAliasesWriteStorage[compIndex] = source.readAliasesWriteStorage[compIndex]; + } + }(), + ...); + base->template AcquireLockReference(); + } + + // Reference a subset of an EntityLock + template() && is_lock_subset() && + (!is_write_allowed() && ...), + int> = 0> + inline Lock(const EntityLock &source) + : instance(source.lock.instance), base(source.lock.base), readAliasesWriteStorage({}) { + base->template AcquireLockReference(); + } + + inline ~Lock() { + base->template ReleaseLockReference(); + } inline constexpr ECS &GetInstance() const { return instance; @@ -114,7 +158,7 @@ namespace Tecs { inline const EntityView EntitiesWith() const { static_assert(!is_global_component(), "Entities can't have global components"); - if (permissions[0]) { + if (readAliasesWriteStorage[0]) { return instance.template Storage().writeValidEntities; } else { return instance.template Storage().readValidEntities; @@ -126,7 +170,7 @@ namespace Tecs { } inline const EntityView Entities() const { - if (permissions[0]) { + if (readAliasesWriteStorage[0]) { return instance.metadata.writeValidEntities; } else { return instance.metadata.readValidEntities; @@ -179,7 +223,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 (readAliasesWriteStorage[0]) { return instance.template BitsetHas(instance.globalWriteMetadata); } else { return instance.template BitsetHas(instance.globalReadMetadata); @@ -204,7 +248,7 @@ namespace Tecs { if (!std::is_const()) base->template SetAccessFlag(true); - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = readAliasesWriteStorage[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; if (!instance.template BitsetHas(metadata)) { if (is_add_remove_allowed()) { base->writeAccessedFlags[0] = true; @@ -214,11 +258,10 @@ namespace Tecs { // Reset value before allowing reading. instance.template Storage().writeComponents[0] = {}; } else { - throw std::runtime_error( - "Missing global component of type: " + std::string(typeid(CompType).name())); + throw std::runtime_error("Missing global component of type: "s + typeid(CompType).name()); } } - if (instance.template BitsetHas(permissions)) { + if (instance.template BitsetHas(readAliasesWriteStorage)) { return instance.template Storage().writeComponents[0]; } else { return instance.template Storage().readComponents[0]; @@ -232,7 +275,7 @@ namespace Tecs { static_assert(is_global_component(), "Only global components can be accessed without an Entity"); if (!instance.template BitsetHas(instance.globalReadMetadata)) { - throw std::runtime_error("Missing global component of type: " + std::string(typeid(CompType).name())); + throw std::runtime_error("Missing global component of type: "s + typeid(CompType).name()); } return instance.template Storage().readComponents[0]; } @@ -243,7 +286,7 @@ namespace Tecs { static_assert(is_global_component(), "Only global components can be accessed without an Entity"); base->template SetAccessFlag(true); - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = readAliasesWriteStorage[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; if (!instance.template BitsetHas(metadata)) { if (is_add_remove_allowed()) { base->writeAccessedFlags[0] = true; @@ -251,7 +294,7 @@ namespace Tecs { metadata[1 + instance.template GetComponentIndex()] = true; instance.template Storage().writeComponents.resize(1); } else { - throw std::runtime_error("Missing global component of type: " + std::string(typeid(T).name())); + throw std::runtime_error("Missing global component of type: "s + typeid(T).name()); } } return instance.template Storage().writeComponents[0] = value; @@ -263,7 +306,7 @@ namespace Tecs { static_assert(is_global_component(), "Only global components can be accessed without an Entity"); base->template SetAccessFlag(true); - auto &metadata = permissions[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; + auto &metadata = readAliasesWriteStorage[0] ? instance.globalWriteMetadata : instance.globalReadMetadata; if (!instance.template BitsetHas(metadata)) { if (is_add_remove_allowed()) { base->writeAccessedFlags[0] = true; @@ -271,7 +314,7 @@ namespace Tecs { metadata[1 + instance.template GetComponentIndex()] = true; instance.template Storage().writeComponents.resize(1); } else { - throw std::runtime_error("Missing global component of type: " + std::string(typeid(T).name())); + throw std::runtime_error("Missing global component of type: "s + typeid(T).name()); } } return instance.template Storage().writeComponents[0] = T(std::forward(args)...); @@ -305,17 +348,91 @@ namespace Tecs { template inline Lock Subset() const { - using NewLockType = Lock; - static_assert(has_permissions(), "Lock types are not a subset of existing permissions."); + static_assert(has_permissions(), + "Lock types are not a subset of existing permissions."); return Lock(*this); } + template + std::optional> TryLock() const { + if constexpr (has_permissions()) { + return Lock(*this); + } else if constexpr (is_add_remove_allowed>()) { + if constexpr (is_add_remove_optional()) { + if (base->writePermissions[0]) { + return Lock(instance, base, readAliasesWriteStorage); + } else { + return {}; + } + } else { + return {}; + } + } else { + bool hasPermissions = ([&]() -> bool { + if constexpr (is_write_allowed>()) { + if constexpr (is_write_optional()) { + return instance.template BitsetHas(base->writePermissions); + } else { + return is_write_allowed(); + } + } else if constexpr (is_read_allowed>()) { + if constexpr (is_read_optional()) { + return instance.template BitsetHas(base->readPermissions); + } else { + return is_read_allowed(); + } + } else { + return true; + } + }() && ...); + if (hasPermissions) { + return Lock(instance, base, readAliasesWriteStorage); + } else { + return {}; + } + } + } + long UseCount() const { return base.use_count(); } private: + // Private constructor for runtime lock conversion + template + inline Lock(ECS &instance, decltype(base) base, decltype(readAliasesWriteStorage) readAliasesWriteStorage) + : instance(instance), base(base), readAliasesWriteStorage(readAliasesWriteStorage) { + if constexpr (!is_add_remove_allowed()) { + if constexpr (is_add_remove_optional()) { + if (!base->writePermissions[0]) { + this->readAliasesWriteStorage[0] = false; + } + } else { + this->readAliasesWriteStorage[0] = false; + } + } + ( // For each AllComponentTypes + [&] { + constexpr auto compIndex = 1 + ECS::template GetComponentIndex(); + if constexpr (is_read_allowed()) { + if constexpr (!is_write_allowed()) { + if constexpr (is_write_optional()) { + if (!base->writePermissions[compIndex]) { + this->readAliasesWriteStorage[compIndex] = false; + } + } else { + this->readAliasesWriteStorage[compIndex] = false; + } + } + } else { + this->readAliasesWriteStorage[compIndex] = false; + } + }(), + ...); + base->template AcquireLockReference(); + } + template inline void AllocateComponents(size_t count) const { if constexpr (!is_global_component()) { @@ -367,56 +484,165 @@ namespace Tecs { template friend class Lock; template - friend class DynamicLock; + friend class EntityLock; friend struct Entity; }; - template typename ECSType, typename... AllComponentTypes, typename... StaticPermissions> - class DynamicLock, StaticPermissions...> - : public Lock, StaticPermissions...> { + template typename ECSType, typename... AllComponentTypes, typename... Permissions> + class EntityLock, Permissions...> { private: using ECS = ECSType; + using LockType = Lock; - const std::bitset<1 + sizeof...(AllComponentTypes)> readPermissions; + const LockType lock; - template - static const auto generateReadBitset() { - std::bitset<1 + sizeof...(AllComponentTypes)> result; - result[0] = true; - ((result[1 + ECS::template GetComponentIndex()] = - is_read_allowed()), - ...); - return result; + public: + const Entity entity; + + template + inline EntityLock(const Lock &source, const Entity &entity) + : lock(source), entity(entity) { + static_assert(!is_add_remove_allowed(), "EntityLock with AddRemove permissions is not supported"); } - template - static const auto generateWriteBitset() { - std::bitset<1 + sizeof...(AllComponentTypes)> result; - result[0] = Tecs::is_add_remove_allowed(); - ((result[1 + ECS::template GetComponentIndex()] = - is_write_allowed()), - ...); - return result; + // Reference a subset of an existing EntityLock + template + inline EntityLock(const EntityLock &source) + : lock(source.lock), entity(source.entity) { + static_assert(LockType::template is_lock_subset(), + "EntityLock must have be a subset of the source EntityLock"); } - public: - template - DynamicLock(const LockType &lock) - : Lock(lock), readPermissions(generateReadBitset()) {} + inline constexpr ECS &GetInstance() const { + return lock.instance; + } + +#ifndef TECS_HEADER_ONLY + inline size_t GetTransactionId() const { + return lock.base->transactionId; + } +#endif + + template + inline const EntityView PreviousEntitiesWith() const { + static_assert(!is_global_component(), "Entities can't have global components"); + return lock.template PreviousEntitiesWith(); + } + + template + inline const EntityView EntitiesWith() const { + static_assert(!is_global_component(), "Entities can't have global components"); + return lock.template EntitiesWith(); + } + + inline const EntityView PreviousEntities() const { + return lock.template PreviousEntities(); + } + + inline const EntityView Entities() const { + return lock.template Entities(); + } + + inline bool Exists() const { + return entity.Exists(lock); + } + + inline bool Existed() const { + return entity.Existed(lock); + } + + template + inline bool Has() const { + if constexpr (all_global_components()) { + return lock.template Had(); + } else { + return entity.Has(lock); + } + } + + template + inline bool Had() const { + if constexpr (all_global_components()) { + return lock.template Had(); + } else { + return entity.Had(lock); + } + } + + template + inline auto &Get() const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + if constexpr (is_global_component()) { + return lock.template Get(); + } else { + return entity.Get(lock); + } + } + + template + inline const T &GetPrevious() const { + using CompType = std::remove_cv_t; + static_assert(is_read_allowed(), "Component is not locked for reading."); + if constexpr (is_global_component()) { + return lock.template GetPrevious(); + } else { + return entity.GetPrevious(lock); + } + } + + template + inline T &Set(T &value) const { + static_assert(is_write_allowed(), "Component is not locked for writing."); + if constexpr (is_global_component()) { + return lock.template Set(value); + } else { + return entity.Set(value, lock); + } + } + + template + inline T &Set(Args &&...args) const { + static_assert(is_write_allowed(), "Component is not locked for writing."); + if constexpr (is_global_component()) { + return lock.template Set(std::forward(args)...); + } else { + return entity.Set(std::forward(args)..., lock); + } + } + + template + inline EntityLock Subset() const { + static_assert(LockType::template has_permissions(), + "Lock types are not a subset of existing permissions."); + + return EntityLock(*this); + } template - std::optional> TryLock() const { - if constexpr (Lock::template has_permissions()) { - return Lock(this->instance, this->base, this->permissions); + std::optional> TryLock() const { + static_assert(!is_add_remove_allowed>(), + "EntityLock with AddRemove permissions is not supported"); + if constexpr (LockType::template has_permissions()) { + return EntityLock(*this); } else { - static const auto requestedRead = generateReadBitset(); - static const auto requestedWrite = generateWriteBitset(); - if ((requestedRead & readPermissions) == requestedRead && - (requestedWrite & this->permissions) == requestedWrite) { - return Lock(this->instance, this->base, this->permissions); + auto optionalLock = lock.template TryLock(); + if (optionalLock) { + return EntityLock(*optionalLock, entity); + } else { + return {}; } - return {}; } } + + long UseCount() const { + return lock.base.use_count(); + } + + template + friend class Lock; + template + friend class EntityLock; + friend struct Entity; }; -}; // namespace Tecs +} // namespace Tecs diff --git a/inc/Tecs_permissions.hh b/inc/Tecs_permissions.hh index 3b242e9..3e8fff5 100644 --- a/inc/Tecs_permissions.hh +++ b/inc/Tecs_permissions.hh @@ -11,8 +11,8 @@ namespace Tecs { template class Lock {}; template - class DynamicLock {}; - template + class EntityLock {}; + template class Transaction {}; /** @@ -43,6 +43,32 @@ namespace Tecs { struct WriteAll {}; struct AddRemove {}; + /** + * Lock permissions can be checked at runtime by wrapping permissions in an Optional<> type. + * + * Examples: + * Lock, Write> lock = ecs.StartTransaction, Write>(); + * // Optional locks can be created from existing required permissions. + * Lock>, Optional>> optionalLock = lock; + * + * // Required permissions will override optional permissions if they overlap. + * Lock, Optional>> == Lock>; + * + * // Only required permissions can be accessed statically. + * Lock> lockA = optionalLock; // Valid + * Lock> lockAB = optionalLock; // Invalid + * + * // Optional permissions can be accessed at runtime using the TryLock() method. + * std::optional>> runtimeLock = optionalLock.TryLock>(); + * if (runtimeLock) { + * // Lock is available. + * } else { + * // Lock is not available. + * } + */ + template + struct Optional {}; + /** * When a component is marked as global by this type trait, it can be accessed without referencing an entity. * Only a single instance of the component will be stored. @@ -91,6 +117,11 @@ namespace Tecs { static constexpr char value[] = (ComponentName); \ }; + template + struct is_entity_lock : std::false_type {}; + template + struct is_entity_lock> : std::true_type {}; + // contains::value is true if T is part of the set Un... template struct contains : std::disjunction...> {}; @@ -110,34 +141,56 @@ namespace Tecs { template struct is_add_remove_allowed : std::false_type {}; - // Lock and DynamicLock specializations + template + struct is_optional : std::false_type {}; + template + struct is_read_optional : std::false_type {}; + template + struct is_write_optional : std::false_type {}; + template + struct is_add_remove_optional : std::false_type {}; + + // Lock specializations // clang-format off template 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...> {}; 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 {}; template struct is_add_remove_allowed> : contains {}; + + template + struct is_read_optional> : std::disjunction...> {}; + template + struct is_read_optional> : std::disjunction...> {}; + + template + struct is_write_optional> : std::disjunction...> {}; + template + struct is_write_optional> : std::disjunction...> {}; + template - struct is_add_remove_allowed> : contains {}; + struct is_add_remove_optional> : std::disjunction...> {}; template - struct is_add_remove_allowed> : contains {}; + struct is_add_remove_optional> : std::disjunction...> {}; + + // Optional specializations + template + struct is_optional> : contains {}; + template + struct is_read_optional> : std::disjunction...> {}; + template + struct is_write_optional> : std::disjunction...> {}; + template + struct is_add_remove_optional> : std::disjunction...> {}; // clang-format on // Check SubLock <= Lock for component type T @@ -151,6 +204,11 @@ namespace Tecs { template struct is_read_allowed> : contains {}; + template + struct is_read_optional> + : std::conjunction>>, + std::disjunction...>> {}; + // ReadAll specialization template struct is_read_allowed : std::true_type {}; @@ -161,6 +219,15 @@ namespace Tecs { template struct is_write_allowed> : contains {}; + template + struct is_read_optional> + : std::conjunction>>, + std::disjunction...>> {}; + template + struct is_write_optional> + : std::conjunction>>, + std::disjunction...>> {}; + // WriteAll specialization template struct is_read_allowed : std::true_type {}; diff --git a/inc/Tecs_storage.hh b/inc/Tecs_storage.hh index 1a6dcda..14a8507 100644 --- a/inc/Tecs_storage.hh +++ b/inc/Tecs_storage.hh @@ -282,17 +282,18 @@ namespace Tecs { traceInfo.Trace(TraceEvent::Type::WriteUnlock); #endif - // Unlock read and write copies + // Release exclusive read lock if a commit lock is held uint32_t current = readers; if (current == READER_LOCKED) { if (!readers.compare_exchange_strong(current, READER_FREE, std::memory_order_release)) { throw std::runtime_error("WriteUnlock readers changed unexpectedly"); } - } #if __cpp_lib_atomic_wait - readers.notify_all(); + readers.notify_all(); #endif + } + // Relase write lock current = writer; if (current != WRITER_LOCKED && current != WRITER_COMMIT) { throw std::runtime_error("WriteUnlock called outside of WriteLock"); @@ -303,6 +304,48 @@ namespace Tecs { writer.notify_all(); #endif +#ifdef TECS_ENABLE_TRACY + tracyWrite.AfterUnlock(); +#endif + } + + // Transitions between a write lock acquired state to a read lock acquired state. + // Read locks cannot transitioned to write locks without starting a new transaction. + inline void WriteToReadLock() { +#ifdef TECS_ENABLE_PERFORMANCE_TRACING + traceInfo.Trace(TraceEvent::Type::WriteToReadLock); +#endif + + // Acquire a shared read lock, releasing the exclusive read lock if a commit lock is held + uint32_t current = readers; + if (current == READER_LOCKED) { + if (!readers.compare_exchange_strong(current, READER_FREE + 1, std::memory_order_release)) { + throw std::runtime_error("WriteToReadLock readers changed unexpectedly"); + } + } else { + current = readers.fetch_add(1, std::memory_order_acquire); + if (current == READER_LOCKED) { + throw std::runtime_error("WriteToReadLock read lock acquire conflict"); + } + } +#if __cpp_lib_atomic_wait + readers.notify_all(); +#endif +#ifdef TECS_ENABLE_TRACY + tracyRead.AfterTryLockShared(true); +#endif + + // Release write lock + current = writer; + if (current != WRITER_LOCKED && current != WRITER_COMMIT) { + throw std::runtime_error("WriteToReadLock called outside of WriteLock"); + } else if (!writer.compare_exchange_strong(current, WRITER_FREE, std::memory_order_release)) { + throw std::runtime_error("WriteToReadLock writer changed unexpectedly"); + } +#if __cpp_lib_atomic_wait + writer.notify_all(); +#endif + #ifdef TECS_ENABLE_TRACY tracyWrite.AfterUnlock(); #endif @@ -313,12 +356,12 @@ namespace Tecs { #endif #ifdef TECS_ENABLE_TRACY static inline const auto tracyReadCtx = []() -> const tracy::SourceLocationData * { - static const std::string lockName = std::string("Read ") + typeid(T *).name(); + static const std::string lockName = "Read "s + typeid(T *).name(); static const tracy::SourceLocationData srcloc{nullptr, lockName.c_str(), __FILE__, __LINE__, 0}; return &srcloc; }; static inline const auto tracyWriteCtx = []() -> const tracy::SourceLocationData * { - static const std::string lockName = std::string("Write ") + typeid(T *).name(); + static const std::string lockName = "Write "s + typeid(T *).name(); static const tracy::SourceLocationData srcloc{nullptr, lockName.c_str(), __FILE__, __LINE__, 0}; return &srcloc; }; @@ -349,7 +392,7 @@ namespace Tecs { template friend class Lock; - template + template friend class Transaction; friend struct Entity; }; diff --git a/inc/Tecs_tracing.hh b/inc/Tecs_tracing.hh index b9cd2f5..0eaa01f 100644 --- a/inc/Tecs_tracing.hh +++ b/inc/Tecs_tracing.hh @@ -16,19 +16,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 { @@ -44,6 +31,7 @@ namespace Tecs { CommitLock, CommitUnlock, WriteUnlock, + WriteToReadLock, }; Type type = Type::Invalid; @@ -65,6 +53,7 @@ namespace Tecs { "CommitLock", "CommitUnlock", "WriteUnlock", + "WriteToReadLock", }; return out << eventTypeNames[(size_t)t]; } diff --git a/inc/Tecs_transaction.hh b/inc/Tecs_transaction.hh index 0c9f34c..76f3a63 100644 --- a/inc/Tecs_transaction.hh +++ b/inc/Tecs_transaction.hh @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace Tecs { @@ -38,9 +39,27 @@ namespace Tecs { * Once a Transaction is deconstructed, all Locks referencing its permissions become invalid. */ template typename ECSType, typename... AllComponentTypes> - class BaseTransaction { + class Transaction> { + private: + using ECS = ECSType; + using EntityMetadata = typename ECS::EntityMetadata; + +#ifdef TECS_ENABLE_TRACY + std::optional tracyZone; +#endif + + ECS &instance; +#ifndef TECS_HEADER_ONLY + size_t transactionId; +#endif + + std::bitset<1 + sizeof...(AllComponentTypes)> readPermissions, writePermissions, writeAccessedFlags; + std::array readLockReferences, writeLockReferences; + public: - BaseTransaction(ECSType &instance) : instance(instance) { + template + inline Transaction(ECS &instance, const LockType &) + : instance(instance), readLockReferences({0}), writeLockReferences({0}) { #ifndef TECS_HEADER_ONLY transactionId = ++nextTransactionId; for (size_t i = 0; i < activeTransactionsCount; i++) { @@ -53,99 +72,57 @@ namespace Tecs { } activeTransactions[activeTransactionsCount++] = instance.ecsId; #endif - } - // Delete copy constructor - BaseTransaction(const BaseTransaction &) = delete; - - 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 EntityMetadata = typename ECS::EntityMetadata; - using FlatPermissions = typename FlattenPermissions::type; - -#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 -#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); #endif + readPermissions[0] = true; + writePermissions[0] = is_add_remove_allowed(); + // clang-format off + (( + readPermissions[1 + instance.template GetComponentIndex()] = is_read_allowed() + ), ...); + (( + writePermissions[1 + instance.template GetComponentIndex()] = is_write_allowed() + ), ...); + // clang-format on + std::bitset<1 + sizeof...(AllComponentTypes)> 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 constexpr (is_add_remove_allowed()) { return instance.metadata.WriteLock(block); } else { return instance.metadata.ReadLock(block); } }, - [&instance](bool block) { - if (is_write_allowed()) { + [&](bool block) { + if constexpr (is_write_allowed()) { return instance.template Storage().WriteLock(block); - } else if (is_read_allowed()) { + } else if constexpr (is_read_allowed()) { return instance.template Storage().ReadLock(block); + } else { + (void)block; // Unreferenced parameter warning on MSVC + // This component type isn't part of the lock, skip. + return true; } - // This component type isn't part of the lock, skip. - return true; }...}; std::array, acquired.size()> unlockFuncs = { - [&instance]() { - if (is_add_remove_allowed()) { + [&]() { + if constexpr (is_add_remove_allowed()) { return instance.metadata.WriteUnlock(); } else { return instance.metadata.ReadUnlock(); } }, - [&instance]() { - if (is_write_allowed()) { + [&]() { + if constexpr (is_write_allowed()) { instance.template Storage().WriteUnlock(); - } else if (is_read_allowed()) { + } else if constexpr (is_read_allowed()) { instance.template Storage().ReadUnlock(); } // This component type isn't part of the lock, skip. @@ -173,42 +150,122 @@ namespace Tecs { } } - if (is_add_remove_allowed()) { + if constexpr (is_add_remove_allowed()) { // 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; + + template + inline void SetAccessFlag(bool value) { + writeAccessedFlags[1 + instance.template GetComponentIndex()] = value; + } + + template + inline void AcquireLockReference() { + readLockReferences[0]++; + if constexpr (is_add_remove_allowed()) { + if (!writePermissions[0]) throw std::runtime_error("AddRemove lock not acquired"); + writeLockReferences[0]++; + } else if constexpr (is_add_remove_optional()) { + if (writePermissions[0]) writeLockReferences[0]++; + } + ( // For each AllComponentTypes + [&] { + constexpr auto compIndex = 1 + ECS::template GetComponentIndex(); + if constexpr (is_write_allowed()) { + if (!writePermissions[compIndex]) throw std::runtime_error("Write lock not acquired"); + writeLockReferences[compIndex]++; + } else if constexpr (is_write_optional()) { + if (writePermissions[compIndex]) writeLockReferences[compIndex]++; + } + if constexpr (is_read_allowed()) { + if (!readPermissions[compIndex]) throw std::runtime_error("Read lock not acquired"); + readLockReferences[compIndex]++; + } else if constexpr (is_read_optional()) { + if (readPermissions[compIndex]) readLockReferences[compIndex]++; + } + }(), + ...); + } + + template + void ReleaseLockReference() { + readLockReferences[0]--; + if constexpr (is_add_remove_allowed()) { + if (!writePermissions[0]) throw std::runtime_error("AddRemove lock not acquired"); + writeLockReferences[0]--; + } else if constexpr (is_add_remove_optional()) { + if (writePermissions[0]) writeLockReferences[0]--; + } + ( // For each AllComponentTypes + [&] { + constexpr auto compIndex = 1 + ECS::template GetComponentIndex(); + if constexpr (is_write_allowed()) { + if (!writePermissions[compIndex]) throw std::runtime_error("Write lock not acquired"); + writeLockReferences[compIndex]--; + } else if constexpr (is_write_optional()) { + if (writePermissions[compIndex]) { + if (!writePermissions[compIndex]) throw std::runtime_error("Write lock not acquired"); + writeLockReferences[compIndex]--; + } + } + if constexpr (is_read_allowed()) { + if (!readPermissions[compIndex]) throw std::runtime_error("Read lock not acquired"); + readLockReferences[compIndex]--; + } else if constexpr (is_read_optional()) { + if (readPermissions[compIndex]) { + if (!readPermissions[compIndex]) throw std::runtime_error("Read lock not acquired"); + readLockReferences[compIndex]--; + } + } + + if (writeAccessedFlags[0] || writeAccessedFlags[compIndex]) return; + if (writePermissions[compIndex]) { + if (writeLockReferences[compIndex] == 0) { + writePermissions[compIndex] = false; + if (readLockReferences[compIndex] == 0) { + readPermissions[compIndex] = false; + instance.template Storage().WriteUnlock(); + } else { + instance.template Storage().WriteToReadLock(); + } + } + } else if (readPermissions[compIndex]) { + if (readLockReferences[compIndex] == 0) { + readPermissions[compIndex] = false; + instance.template Storage().ReadUnlock(); + } + } + }(), + ...); } inline ~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 an AddRemove was performed, update the entity validity metadata + if (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(); + [this] { + if (instance.template BitsetHas(writePermissions)) { + if (!instance.template BitsetHas(writeAccessedFlags)) { + instance.template Storage().WriteUnlock(); } - } else if constexpr (is_read_allowed()) { - this->instance.template Storage().ReadUnlock(); + } else if (instance.template BitsetHas(readPermissions)) { + instance.template Storage().ReadUnlock(); } }(), ...); @@ -217,15 +274,13 @@ namespace Tecs { #ifdef TECS_ENABLE_TRACY ZoneNamedN(tracyCommitScope1, "CommitLock", true); #endif - if constexpr (is_add_remove_allowed()) { - if (this->writeAccessedFlags[0]) this->instance.metadata.CommitLock(); + if (writeAccessedFlags[0]) { // If AddRemove performed + instance.metadata.CommitLock(); } ( // For each AllComponentTypes - [&] { - if constexpr (is_write_allowed()) { - if (this->instance.template BitsetHas(this->writeAccessedFlags)) { - this->instance.template Storage().CommitLock(); - } + [this] { + if (instance.template BitsetHas(writeAccessedFlags)) { + instance.template Storage().CommitLock(); } }(), ...); @@ -234,33 +289,28 @@ namespace Tecs { #ifdef TECS_ENABLE_TRACY 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 (writeAccessedFlags[0]) { // If AddRemove performed + // 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()) { - // Skip if no write accesses were made - if (!this->instance.template BitsetHas(this->writeAccessedFlags)) return; - auto &storage = this->instance.template Storage(); + ( // For each modified component type, swap read and write storage, and release commit lock. + [this] { + using CompType = AllComponentTypes; + if (instance.template BitsetHas(writeAccessedFlags)) { + 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 (writeAccessedFlags[0]) { // If AddRemove performed + storage.readValidEntities.swap(storage.writeValidEntities); } storage.CommitUnlock(); } @@ -268,16 +318,15 @@ namespace Tecs { ...); } - ( // For each AllComponentTypes, reset the write storage to match read. - [&] { - if constexpr (is_write_allowed()) { - // Skip if no write accesses were made - if (!this->instance.template BitsetHas(this->writeAccessedFlags)) return; - auto &storage = this->instance.template Storage(); + ( // For each modified component type, reset the write storage to match read. + [this] { + using CompType = AllComponentTypes; + if (instance.template BitsetHas(writeAccessedFlags)) { + auto &storage = instance.template Storage(); - if constexpr (is_global_component()) { + if (is_global_component()) { storage.writeComponents = storage.readComponents; - } else if (is_add_remove_allowed() && this->writeAccessedFlags[0]) { + } else if (writeAccessedFlags[0]) { // If AddRemove performed storage.writeComponents = storage.readComponents; storage.writeValidEntities = storage.readValidEntities; } else { @@ -295,21 +344,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 (writePermissions[0]) { // If AddRemove allowed + if (writeAccessedFlags[0]) { // If AddRemove performed + 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(); } #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 +#ifndef TECS_HEADER_ONLY + auto start = activeTransactions.begin(); + activeTransactionsCount = std::remove(start, start + activeTransactionsCount, instance.ecsId) - start; #endif } @@ -318,30 +369,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)); @@ -356,46 +406,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), @@ -410,5 +460,11 @@ namespace Tecs { } } } + + template + friend class Lock; + template + friend class EntityLock; + friend struct Entity; }; }; // namespace Tecs diff --git a/tests/tests.cpp b/tests/tests.cpp index 99f5cb0..bf7fd42 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -7,12 +7,13 @@ #include #include #include -#include #include #include #include +#include using namespace testing; +using namespace std::string_literals; static ECS ecs; @@ -170,10 +171,6 @@ int main(int /* argc */, char ** /* argv */) { e.Set