From 3e0740d70c25bda321b4d31e86fbb906af6a92e9 Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 16:58:44 +0100 Subject: [PATCH 01/14] Working Deferred removal of ids --- Include/Misc/PipeDebug.h | 10 +- Include/Pipe/Core/PageBuffer.h | 5 +- Include/PipeECS.h | 576 +++++++++++++++++++-------------- Include/PipeECSFwd.h | 2 +- Src/PipeECS.cpp | 322 +++++++++++------- Tests/ECS/Components.spec.cpp | 6 +- Tests/ECS/Filtering.spec.cpp | 19 +- 7 files changed, 577 insertions(+), 363 deletions(-) diff --git a/Include/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index 7410f249..b211131a 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -136,9 +136,9 @@ namespace p TArray inspectors{1}; // Updated on tick - TArray includePools; - TArray excludePools; - TArray previewPools; + TArray includePools; + TArray excludePools; + TArray previewPools; // Layout bool resetLayout = true; @@ -747,7 +747,7 @@ namespace p ImGui::PushStyleCompact(); static String poolName; ImGui::PushButtonColor(previewColor); - for (const BasePool* previewPool : ecsDbg.previewPools) + for (const IPool* previewPool : ecsDbg.previewPools) { if (previewPool && previewPool->Has(id)) { @@ -1008,7 +1008,7 @@ namespace p FindAllIdsWith(ecsDbg.includePools, ids); } - for (const BasePool* pool : ecsDbg.excludePools) + for (const IPool* pool : ecsDbg.excludePools) { ExcludeIdsWithStable(pool, ids, false); } diff --git a/Include/Pipe/Core/PageBuffer.h b/Include/Pipe/Core/PageBuffer.h index a52dc812..db1ac925 100644 --- a/Include/Pipe/Core/PageBuffer.h +++ b/Include/Pipe/Core/PageBuffer.h @@ -118,9 +118,10 @@ namespace p Type* At(sizet index) { - if (Type* page = pages[GetPage(index)]) + const i32 pageIndex = GetPage(index); + if (pageIndex < pages.Size() && pages[pageIndex]) { - return page + GetOffset(index); + return pages[pageIndex] + GetOffset(index); } return nullptr; } diff --git a/Include/PipeECS.h b/Include/PipeECS.h index 89e05d7b..f28748b4 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -28,14 +28,24 @@ namespace p //////////////////////////////// - // ENTITY IDS + // DECLARATIONS // + enum class RmIdFlags : u8 + { + // Default: Deferred & keep children + None = 0, + // Remove entities instantly instead of waiting (deferred) + Instant = 1 << 0, + // Remove children entities of the ones being removed + RemoveChildren = 1 << 1 + }; + P_DEFINE_FLAG_OPERATORS(RmIdFlags) + + #pragma region Ids /** An Id is an integer composed of both index and version that identifies an entity */ struct P_API Id { -#pragma warning(push) -#pragma warning(disable:4201) // Avoid warning about nameless struct #if P_ID_IS_64BIT using Value = u64; using Index = u32; @@ -43,17 +53,7 @@ namespace p using Difference = i64; static constexpr Index indexMask = 0xffffffff; static constexpr Version versionMask = 0xffffffff; - - protected: - union - { - struct - { - Index index; - Version version; - }; - Value raw; - }; + static constexpr sizet indexShift = 32u; #else using Value = u32; using Index = u32; @@ -61,51 +61,51 @@ namespace p using Difference = i64; static constexpr Index indexMask = 0xfffff; static constexpr Version versionMask = 0xfff; - - protected: - union - { - struct - { - Index index:20; - Version version:12; - }; - Value raw; - }; + static constexpr sizet indexShift = 20u; #endif -#pragma warning(pop) + + Value value; private: - constexpr Id(Value raw) : raw(raw) {} + explicit constexpr Id(Value value) : value(value) {} public: - constexpr Id() : index{indexMask}, version{versionMask} {} - constexpr Id(Index index, Version version) : index{index}, version{version} {} + constexpr Id() : Id(indexMask, versionMask) {} + constexpr Id(Index index, Version version) + : value{(index & indexMask) | ((Value)version << indexShift)} + {} + constexpr Id(const Id& other) : Id(other.value) {} + constexpr Id& operator=(const Id& other) + { + value = other.value; + return *this; + } + constexpr bool operator==(const Id other) const { - return raw == other.raw; + return value == other.value; } constexpr bool operator!=(const Id other) const { - return raw != other.raw; + return value != other.value; } constexpr bool operator<(const Id other) const { - return raw < other.raw; + return value < other.value; } constexpr bool operator<=(const Id other) const { - return raw <= other.raw; + return value <= other.value; } constexpr bool operator>(const Id other) const { - return raw > other.raw; + return value > other.value; } constexpr bool operator>=(const Id other) const { - return raw >= other.raw; + return value >= other.value; } static constexpr Id MakeRaw(Value raw) @@ -113,23 +113,20 @@ namespace p return Id(raw); } - constexpr Value GetRaw() const - { - return raw; - } constexpr Index GetIndex() const { - return index; + return value & indexMask; } constexpr Version GetVersion() const { - return version; + constexpr auto mask = versionMask << indexShift; + return (value & mask) >> indexShift; } }; inline sizet GetHash(const Id id) { - return GetHash(id.GetRaw()); + return GetHash(id.value); } // Creates an id from a combination of index and version. This does NOT create an entity. @@ -169,54 +166,9 @@ namespace p * valid version */ P_API Id IdFromString(String str, EntityContext* context); - - - /** IdRegistry tracks the existance and versioning of ids. Used internally by the ECS - * context */ - struct P_API IdRegistry - { - using Index = Id::Index; - using Version = Id::Version; - - private: - - TArray entities; - TArray available; - - // Support for multi-threaded entity creation and removal - mutable std::shared_mutex mutex; - - - public: - - IdRegistry() {} - IdRegistry(IdRegistry&& other); - IdRegistry(const IdRegistry& other); - IdRegistry& operator=(IdRegistry&& other); - IdRegistry& operator=(const IdRegistry& other); - - - Id Create(); - void Create(TView newIds); - bool Destroy(TView ids); - bool IsValid(Id id) const; - bool WasRemoved(Id id) const; - TOptional GetValidVersion(Index idx) const; - - u32 Size() const - { - return entities.Size() - available.Size(); - } - - template - void Each(Callback cb) const; - }; #pragma endregion Ids -//////////////////////////////// -// COMPONENTS -// #pragma region Components /** * Modified component @@ -226,18 +178,18 @@ namespace p template struct CMdfd { - P_STRUCT(CMdfd) + P_STRUCT(CMdfd, TF_NotSerialized) }; /** * Removed component * Optionally, an entity can be marked removed instead of actually removed and then flushed * manually along with all other removed entities. - * @see RemoveId() + * @see RmId() */ struct P_API CRemoved { - P_STRUCT(CRemoved) + P_STRUCT(CRemoved, TF_NotSerialized) }; struct P_API CParent @@ -262,6 +214,63 @@ namespace p #pragma endregion Components + //////////////////////////////// + // COMPONENTS + // +#pragma region Id Register + /** IdRegistry tracks the existance and versioning of ids. Used internally by the ECS + * context */ + struct P_API IdRegistry + { + using Index = Id::Index; + using Version = Id::Version; + + private: + TArray entities; + TArray available; + TArray deferredRemoves; // List of ids that are invalid but not removed yet. + + Arena* arena = nullptr; + + // Support for multi-threaded entity creation and removal + mutable std::shared_mutex mutex; + + + public: + + IdRegistry(Arena& arena = GetCurrentArena()) + : entities{arena}, available{arena}, deferredRemoves{arena}, arena{&arena} + {} + IdRegistry(IdRegistry&& other); + IdRegistry(const IdRegistry& other); + IdRegistry& operator=(IdRegistry&& other); + IdRegistry& operator=(const IdRegistry& other); + + + Id Create(); + void Create(TView newIds); + bool Remove(TView ids); + bool DeferredRemove(TView ids); + void FlushDeferredRemoves(); + const TArray& GetDeferredRemovals() const + { + return deferredRemoves; + } + bool IsValid(Id id) const; + bool WasRemoved(Id id) const; + TOptional GetValidVersion(Index idx) const; + + u32 Size() const + { + return entities.Size() - available.Size(); + } + + template + void Each(Callback cb) const; + }; +#pragma endregion Id Register + + //////////////////////////////// // SERIALIZATION // @@ -477,15 +486,60 @@ namespace p } }; - - struct P_API BasePool + struct P_API IPool { - using Index = Id::Index; - + using Index = Id::Index; using Iterator = PoolIterator; using ReverseIterator = std::reverse_iterator; + protected: + TypeId typeId; + + + public: + IPool(TypeId typeId) : typeId{typeId} {} + virtual ~IPool() {} + + // Returns the data pointer of a component if contained + virtual bool Has(Id id) const = 0; + virtual i32 Size() const = 0; + virtual void* AddDefault(Id id) = 0; + virtual bool Remove(Id id) = 0; + virtual void RemoveUnsafe(Id id) = 0; + virtual i32 Remove(TView ids) = 0; + virtual void RemoveUnsafe(TView ids) = 0; + virtual void* TryGetVoid(Id id) = 0; + virtual void Clear() = 0; + virtual TUniquePtr Clone() = 0; + virtual const TArray& GetIdList() const = 0; + + bool IsEmpty() const + { + return Size() > 0; + } + + TypeId GetTypeId() const + { + return typeId; + } + + // Standard functions: + public: + Iterator begin() const; + Iterator end() const; + Iterator cbegin() const; + Iterator cend() const; + ReverseIterator rbegin() const; + ReverseIterator rend() const; + ReverseIterator crbegin() const; + ReverseIterator crend() const; + }; + + i32 GetSmallestPool(TView pools); + + struct P_API ComponentPool : public IPool + { protected: TPageBuffer idIndices; TArray idList; @@ -493,50 +547,35 @@ namespace p i32 lastRemovedIndex = NO_INDEX; PoolRemovePolicy removePolicy; - TypeId typeId; - EntityContext* context = nullptr; - TBroadcast> onAdd; - TBroadcast> onRemove; + TBroadcast> onAdd; + TBroadcast> onRemove; - BasePool(TypeId typeId, EntityContext& ast, PoolRemovePolicy removePolicy, Arena& arena) - : idIndices{arena} - , idList{arena} - , arena{&arena} - , removePolicy{removePolicy} - , typeId{typeId} - , context{&ast} - { - BindOnPageAllocated(); - } - BasePool(const BasePool& other); - BasePool(BasePool&& other) noexcept; - BasePool& operator=(BasePool&& other) noexcept; + ComponentPool(TypeId typeId, PoolRemovePolicy removePolicy, Arena& arena); + ComponentPool(const ComponentPool& other); + ComponentPool(ComponentPool&& other) noexcept; + ComponentPool& operator=(const ComponentPool& other) noexcept; + ComponentPool& operator=(ComponentPool&& other) noexcept; void OnAdded(TView ids) { - onAdd.Broadcast(*context, ids); + if (!ids.IsEmpty()) + { + onAdd.Broadcast(ids); + } } void OnRemoved(TView ids) { if (!ids.IsEmpty()) { - onRemove.Broadcast(*context, ids); + onRemove.Broadcast(ids); } } public: - virtual ~BasePool() {} + virtual ~ComponentPool() {} - // Returns the data pointer of a component if contianed - virtual void* TryGetVoid(Id id) = 0; - - virtual void* AddDefault(Id id) = 0; - virtual bool Remove(Id id) = 0; - virtual void RemoveUnsafe(Id id) = 0; - virtual i32 Remove(TView ids) = 0; - virtual void RemoveUnsafe(TView ids) = 0; inline i32 GetIndexFromId(const Index index) const { @@ -551,22 +590,7 @@ namespace p return index < idList.Size() ? idList[index] : NoId; } - virtual void SetOwnerContext(EntityContext& destination) - { - context = &destination; - } - virtual TUniquePtr Clone() = 0; - - TypeId GetTypeId() const - { - return typeId; - } - EntityContext& GetContext() const - { - return *context; - } - - bool Has(Id id) const + bool Has(Id id) const override { const i32* const index = idIndices.At(id.GetIndex()); return index && *index != NO_INDEX; @@ -574,37 +598,35 @@ namespace p Iterator Find(const Id id) const { - return Has(id) ? Iterator{idList, GetIndexFromId(id)} : end(); + const i32* const index = idIndices.At(id.GetIndex()); + return index && *index != NO_INDEX ? Iterator{idList, *index} : end(); } - i32 Size() const + i32 Size() const override { return idList.Size(); } bool IsEmpty() const { - return idList.IsEmpty(); + return Size() > 0; } - // Return pointer to internal list of ids - Id* Data() const + const TArray& GetIdList() const override { - return idList.Data(); + return idList; } - TBroadcast>& OnAdd() + TBroadcast>& OnAdd() { return onAdd; } - TBroadcast>& OnRemove() + TBroadcast>& OnRemove() { return onRemove; } - virtual void Clear() = 0; - protected: Index EmplaceId(const Id id, bool forceBack); @@ -615,65 +637,51 @@ namespace p void ClearIds(); - - // Standard functions: - public: - - Iterator begin() const - { - return Iterator{idList, static_cast(idList.Size())}; - } - Iterator end() const - { - return Iterator{idList, {}}; - } - Iterator cbegin() const - { - return begin(); - } - Iterator cend() const - { - return end(); - } - ReverseIterator rbegin() const - { - return std::make_reverse_iterator(end()); - } - ReverseIterator rend() const - { - return std::make_reverse_iterator(begin()); - } - ReverseIterator crbegin() const - { - return rbegin(); - } - ReverseIterator crend() const - { - return rend(); - } - private: void BindOnPageAllocated(); }; - i32 GetSmallestPool(TView pools); - - template - struct TPool : public BasePool + struct TPool : public ComponentPool { private: TPageBuffer data; public: - TPool(EntityContext& ast, Arena& arena = GetCurrentArena()) - : BasePool(p::GetTypeId(), ast, PoolRemovePolicy::InPlace, arena), data{arena} + TPool(p::EntityContext& ctx, Arena& arena = GetCurrentArena()) + : ComponentPool(p::GetTypeId(), PoolRemovePolicy::InPlace, arena), data{arena} {} - TPool(const TPool& other) : BasePool(other), data{*other.arena} + TPool(const TPool& other) : ComponentPool(other), data{*other.arena} + { + if constexpr (!p::IsEmpty) + { + data.Reserve(other.data.Capacity()); + i32 u = 0; + for (i32 i = 0; i < other.Size(); ++i, ++u) + { + const Id id = other.idList[i]; + if (id != NoId) + { + if constexpr (IsCopyConstructible) + { + data.Insert(u, other.data[i]); + } + else + { + data.Insert(u); + } + } + } + } + } + TPool& operator=(const TPool& other) { + ComponentPool::operator=(other); + data = TPageBuffer{*other.arena}; + if constexpr (!p::IsEmpty) { data.Reserve(other.data.Capacity()); @@ -694,6 +702,7 @@ namespace p } } } + return *this; } ~TPool() override { @@ -917,16 +926,28 @@ namespace p T* TryGet(Id id) { - if (!p::IsEmpty && Has(id)) + if constexpr (!p::IsEmpty) { - return &data[GetIndexFromId(id)]; + const i32* const index = idIndices.At(id.GetIndex()); + if (index && *index != NO_INDEX) // Has(id) + { + return &data[*index]; + } } return nullptr; } const T* TryGet(Id id) const { - return Has(id) ? &data[GetIndexFromId(id)] : nullptr; + if constexpr (!p::IsEmpty) + { + const i32* const index = idIndices.At(id.GetIndex()); + if (index && *index != NO_INDEX) // Has(id) + { + return &data[*index]; + } + } + return nullptr; } void* TryGetVoid(Id id) override @@ -934,7 +955,7 @@ namespace p return TryGet(id); } - TUniquePtr Clone() override + TUniquePtr Clone() override { return p::MakeUnique>(*this); } @@ -1036,19 +1057,108 @@ namespace p } }; + // CRemoved pool is special in the sense that it acts as an interface to deferred removals in + // the IdRegistry + template<> + struct P_API TPool : public IPool + { + using T = CRemoved; + + p::IdRegistry* idRegistry = nullptr; + + public: + TPool(p::EntityContext& ctx, Arena& arena = GetCurrentArena()); + TPool(const TPool& other) : IPool(p::GetTypeId()), idRegistry{other.idRegistry} {} + TPool& operator=(const TPool& other) + { + return *this; + } + ~TPool() + { + Clear(); + } + + bool Has(Id id) const override + { + return GetIdList().Contains(id); + } + + i32 Size() const override; + + bool IsEmpty() const + { + return Size() > 0; + } + + void* AddDefault(Id id) override + { + Add(id, {}); + return nullptr; + } + + void Add(Id id, CRemoved); + + template + void Add(It first, It last, const T& value = {}) requires(IsCopyConstructible) + {} + + template + void Add(It first, It last, CIt from) + requires(IsSame::value_type>, T>) + {} + + bool Remove(Id id) override + { + return false; + } + + void RemoveUnsafe(Id id) override {} + + i32 Remove(TView ids) override + { + return 0; + } + + void RemoveUnsafe(TView ids) override {} + + T* TryGet(Id id) + { + return nullptr; + } + + const T* TryGet(Id id) const + { + return nullptr; + } + + void* TryGetVoid(Id id) override + { + return nullptr; + } + + TUniquePtr Clone() override + { + return {}; // Can not clone + } + + void Clear() override {} + + const TArray& GetIdList() const override; + }; + struct P_API PoolInstance { TypeId componentId{}; - TUniquePtr pool; + TUniquePtr pool; - PoolInstance(TypeId componentId, TUniquePtr&& pool); + PoolInstance(TypeId componentId, TUniquePtr&& pool); PoolInstance(PoolInstance&& other) noexcept; explicit PoolInstance(const PoolInstance& other); PoolInstance& operator=(PoolInstance&& other) noexcept; PoolInstance& operator=(const PoolInstance& other); TypeId GetId() const; - BasePool* GetPool() const; + IPool* GetPool() const; bool operator<(const PoolInstance& other) const; }; #pragma endregion Pools @@ -1103,9 +1213,6 @@ namespace p EntityContext& operator=(EntityContext&& other) noexcept; #pragma region Entities - void Destroy(Id id); - void Destroy(TView ids); - // Reflection helpers void* AddDefault(TypeId typeId, Id id); void Remove(TypeId typeId, Id id); @@ -1284,13 +1391,13 @@ namespace p void Reset(bool keepStatics = false); template - TBroadcast>& OnAdd() + TBroadcast>& OnAdd() { return AssurePool().OnAdd(); } template - TBroadcast>& OnRemove() + TBroadcast>& OnRemove() { return AssurePool().OnRemove(); } @@ -1299,11 +1406,11 @@ namespace p template TPool>& AssurePool() const; - BasePool* GetPool(TypeId componentId) const; - void GetPools(TView componentIds, TArray& outPools) const; - void GetPools(TView componentIds, TArray& outPools) const + IPool* GetPool(TypeId componentId) const; + void GetPools(TView componentIds, TArray& outPools) const; + void GetPools(TView componentIds, TArray& outPools) const { - GetPools(componentIds, reinterpret_cast&>(outPools)); + GetPools(componentIds, reinterpret_cast&>(outPools)); } template @@ -1662,7 +1769,7 @@ namespace p EntityContext& ast; TArray types; - TArray pools; + TArray pools; public: @@ -1700,68 +1807,67 @@ namespace p #pragma region Filtering /** Remove ids containing a component from 'ids'. Does not guarantee order. */ - P_API void ExcludeIdsWith( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); + P_API void ExcludeIdsWith(const IPool* pool, TArray& ids, const bool shouldShrink = true); /** Remove ids containing a component from 'ids'. Guarantees order. */ P_API void ExcludeIdsWithStable( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); + const IPool* pool, TArray& ids, const bool shouldShrink = true); /** Remove ids NOT containing a component from 'ids'. Does not guarantee order. */ P_API void ExcludeIdsWithout( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); + const IPool* pool, TArray& ids, const bool shouldShrink = true); /** Remove ids NOT containing a component from 'ids'. Guarantees order. */ P_API void ExcludeIdsWithoutStable( - const BasePool* pool, TArray& ids, const bool shouldShrink = true); + const IPool* pool, TArray& ids, const bool shouldShrink = true); /** Find ids containing a component from a list 'source' into 'results'. */ - P_API void FindIdsWith(const BasePool* pool, TView source, TArray& results); + P_API void FindIdsWith(const IPool* pool, TView source, TArray& results); P_API void FindIdsWith( - TView pools, TView source, TArray& results); + TView pools, TView source, TArray& results); /** Find ids NOT containing a component from a list 'source' into 'results'. */ - P_API void FindIdsWithout(const BasePool* pool, TView source, TArray& results); + P_API void FindIdsWithout(const IPool* pool, TView source, TArray& results); /** * Find and remove ids containing a component from list 'source' into 'results'. * Does not guarantee order. */ - P_API void ExtractIdsWith(const BasePool* pool, TArray& source, TArray& results, - const bool shouldShrink = true); + P_API void ExtractIdsWith( + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink = true); /** * Find and remove ids containing a component from list 'source' into 'results'. * Guarantees order. */ - P_API void ExtractIdsWithStable(const BasePool* pool, TArray& source, TArray& results, - const bool shouldShrink = true); + P_API void ExtractIdsWithStable( + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink = true); /** * Find and remove ids containing a component from list 'source' into 'results'. * Does not guarantee order. */ - P_API void ExtractIdsWithout(const BasePool* pool, TArray& source, TArray& results, - const bool shouldShrink = true); + P_API void ExtractIdsWithout( + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink = true); /** * Find and remove ids not containing a component from list 'source' into 'results'. * Guarantees order. */ - P_API void ExtractIdsWithoutStable(const BasePool* pool, TArray& source, - TArray& results, const bool shouldShrink = true); + P_API void ExtractIdsWithoutStable( + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink = true); /** Find all ids containing all of the components */ - P_API void FindAllIdsWith(TView pools, TArray& ids); + P_API void FindAllIdsWith(TView pools, TArray& ids); /** Find all ids containing any of the components. Includes possible duplicates */ - P_API void FindAllIdsWithAny(TView pools, TArray& ids); + P_API void FindAllIdsWithAny(TView pools, TArray& ids); /** Find all ids containing any of the components. Prevents duplicates */ - P_API void FindAllIdsWithAnyUnique(TView pools, TArray& ids); + P_API void FindAllIdsWithAnyUnique(TView pools, TArray& ids); // Templated API @@ -2074,7 +2180,7 @@ namespace p { if constexpr (sizeof...(C) == 1) // Only one component? { - const BasePool* pool = access.template GetPool(); + const IPool* pool = access.template GetPool(); if (pool && pool->Size() > 0) { return *pool->begin(); @@ -2097,12 +2203,11 @@ namespace p #pragma region Editing // Create - P_API Id CreateId(EntityContext& ctx); - P_API void CreateIds(EntityContext& ctx, TView Ids); + P_API Id AddId(EntityContext& ctx); + P_API void AddId(EntityContext& ctx, TView Ids); // Remove - - + P_API void RmId(EntityContext& ctx, TView ids, RmIdFlags flags = RmIdFlags::None); #pragma endregion Editing @@ -2165,9 +2270,6 @@ namespace p // void CopyDeep(Tree& ast, const TArray& rootNodes, TArray& outNewRootNodes); // void CopyAndTransferAllChildrenDeep(Tree& ast, Id root, Id otherRoot); - P_API void RemoveId( - TAccessRef, TWrite> access, TView nodes, bool deep = false); - /** * Iterates children nodes making sure child->parent links are correct or fixed * Only first depth links are affected @@ -2350,7 +2452,7 @@ namespace p index = pools.Add(CreatePoolInstance()); } - BasePool* pool = pools[index].GetPool(); + IPool* pool = pools[index].GetPool(); return *static_cast>*>(pool); } diff --git a/Include/PipeECSFwd.h b/Include/PipeECSFwd.h index 89f8d1e8..41df992f 100644 --- a/Include/PipeECSFwd.h +++ b/Include/PipeECSFwd.h @@ -18,7 +18,7 @@ namespace p::ecs enum class PoolDeletionPolicy : u8; struct PoolIterator; - struct BasePool; + struct IPool; template struct TPool; struct PoolInstance; diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index f67cf64d..0abc4e81 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -43,27 +43,31 @@ namespace p IdRegistry::IdRegistry(IdRegistry&& other) { std::unique_lock lock{other.mutex}; - entities = Move(other.entities); - available = Move(other.available); + entities = Move(other.entities); + available = Move(other.available); + deferredRemoves = Move(other.deferredRemoves); } IdRegistry::IdRegistry(const IdRegistry& other) { std::shared_lock lock{other.mutex}; - entities = other.entities; - available = other.available; + entities = other.entities; + available = other.available; + deferredRemoves = other.deferredRemoves; } IdRegistry& IdRegistry::operator=(IdRegistry&& other) { std::unique_lock lock{other.mutex}; - entities = Move(other.entities); - available = Move(other.available); + entities = Move(other.entities); + available = Move(other.available); + deferredRemoves = Move(other.deferredRemoves); return *this; } IdRegistry& IdRegistry::operator=(const IdRegistry& other) { std::shared_lock lock{other.mutex}; - entities = other.entities; - available = other.available; + entities = other.entities; + available = other.available; + deferredRemoves = other.deferredRemoves; return *this; } @@ -106,10 +110,10 @@ namespace p entities.Append(newIds); } - bool IdRegistry::Destroy(TView ids) + bool IdRegistry::Remove(TView ids) { std::unique_lock lock{mutex}; - available.Reserve(available.Size() + ids.Size()); + available.ReserveMore(ids.Size()); const u32 lastAvailable = available.Size(); for (Id id : ids) { @@ -128,6 +132,38 @@ namespace p return (available.Size() - lastAvailable) > 0; } + bool IdRegistry::DeferredRemove(TView ids) + { + std::unique_lock lock{mutex}; + deferredRemoves.ReserveMore(ids.Size()); + const u32 lastPending = deferredRemoves.Size(); + for (Id id : ids) + { + const Index index = id.GetIndex(); + if (entities.IsValidIndex(index)) + { + Id& storedId = entities[index]; + if (id == storedId) + { + deferredRemoves.Add(storedId); + // Increase version to invalidate current entity + storedId = MakeId(index, storedId.GetVersion() + 1u); + } + } + } + return (deferredRemoves.Size() - lastPending) > 0; + } + + void IdRegistry::FlushDeferredRemoves() + { + available.ReserveMore(deferredRemoves.Size()); + for (Id id : deferredRemoves) + { + available.Add(id.GetIndex()); + } + deferredRemoves.Clear(); + } + bool IdRegistry::IsValid(Id id) const { std::shared_lock lock{mutex}; @@ -170,12 +206,12 @@ namespace p Id& entity = entities[i]; if (entity == NoId) { - entity = CreateId(context); + entity = AddId(context); } ids[i] = entity; } // Create all non-root entities - CreateIds(context, {ids.Data() + maxSize, ids.Data() + ids.Size()}); + AddId(context, {ids.Data() + maxSize, ids.Data() + ids.Size()}); if (EnterNext("components")) { @@ -201,7 +237,7 @@ namespace p Id parent = GetIdParent(context, entity); if (entity == NoId) { - entity = CreateId(context); + entity = AddId(context); } ids.Assign(&entity, 1); @@ -329,15 +365,54 @@ namespace p } - BasePool::BasePool(const BasePool& other) - : idIndices{*other.arena} - , idList{*other.arena} - , arena{other.arena} - , removePolicy{other.removePolicy} - , typeId{other.typeId} - , context{other.context} + IPool::Iterator IPool::begin() const + { + const TArray& idList = GetIdList(); + return Iterator{idList, static_cast(idList.Size())}; + } + IPool::Iterator IPool::end() const + { + return Iterator{GetIdList(), {}}; + } + IPool::Iterator IPool::cbegin() const + { + return begin(); + } + IPool::Iterator IPool::cend() const + { + return end(); + } + IPool::ReverseIterator IPool::rbegin() const + { + return std::make_reverse_iterator(end()); + } + IPool::ReverseIterator IPool::rend() const + { + return std::make_reverse_iterator(begin()); + } + IPool::ReverseIterator IPool::crbegin() const + { + return rbegin(); + } + IPool::ReverseIterator IPool::crend() const + { + return rend(); + } + + + ComponentPool::ComponentPool(TypeId typeId, PoolRemovePolicy removePolicy, Arena& arena) + : IPool(typeId), idIndices{arena}, idList{arena}, arena{&arena}, removePolicy{removePolicy} { BindOnPageAllocated(); + } + + ComponentPool::ComponentPool(const ComponentPool& other) + : IPool(other.typeId), idIndices{*other.arena}, idList{*other.arena} + { + arena = other.arena; + removePolicy = other.removePolicy; + typeId = other.typeId; + BindOnPageAllocated(); idList.Reserve(other.idList.Size()); idIndices.Reserve(other.idIndices.Capacity()); for (i32 i = 0; i < other.Size(); ++i) @@ -350,28 +425,51 @@ namespace p } } - BasePool::BasePool(BasePool&& other) noexcept - : idIndices{Move(other.idIndices)} - , idList{Move(other.idList)} - , arena{other.arena} - , lastRemovedIndex{Exchange(other.lastRemovedIndex, NO_INDEX)} - , removePolicy{other.removePolicy} - , typeId{Exchange(other.typeId, {})} - , context{other.context} - {} - BasePool& BasePool::operator=(BasePool&& other) noexcept + ComponentPool::ComponentPool(ComponentPool&& other) noexcept + : IPool(other.typeId), idIndices{Move(other.idIndices)}, idList{Move(other.idList)} + { + BindOnPageAllocated(); + arena = other.arena; + lastRemovedIndex = Exchange(other.lastRemovedIndex, NO_INDEX); + removePolicy = other.removePolicy; + typeId = other.typeId; + } + + ComponentPool& ComponentPool::operator=(const ComponentPool& other) noexcept + { + typeId = other.typeId; + idIndices = {*other.arena}; + idList = {*other.arena}; + arena = other.arena; + removePolicy = other.removePolicy; + typeId = other.typeId; + BindOnPageAllocated(); + idList.Reserve(other.idList.Size()); + idIndices.Reserve(other.idIndices.Capacity()); + for (i32 i = 0; i < other.Size(); ++i) + { + const Id id = other.idList[i]; + if (id.GetVersion() != NoIdVersion) + { + EmplaceId(id, true); + } + } + return *this; + } + + ComponentPool& ComponentPool::operator=(ComponentPool&& other) noexcept { + typeId = other.typeId; idIndices = Move(other.idIndices); idList = Move(other.idList); arena = other.arena; lastRemovedIndex = Exchange(other.lastRemovedIndex, NO_INDEX); removePolicy = other.removePolicy; typeId = other.typeId; - context = other.context; return *this; } - BasePool::Index BasePool::EmplaceId(const Id id, bool forceBack) + ComponentPool::Index ComponentPool::EmplaceId(const Id id, bool forceBack) { P_CheckMsg(!Has(id), "Set already contains entity"); const auto idIndex = id.GetIndex(); @@ -393,7 +491,7 @@ namespace p } } - void BasePool::PopId(Id id) + void ComponentPool::PopId(Id id) { const Index index = id.GetIndex(); i32& idIndex = idIndices[index]; @@ -403,7 +501,7 @@ namespace p idIndex = NO_INDEX; } - void BasePool::PopSwapId(Id id) + void ComponentPool::PopSwapId(Id id) { i32& idIndex = idIndices[id.GetIndex()]; idList.RemoveAtSwapUnsafe(idIndex); @@ -414,7 +512,7 @@ namespace p lastIndex = NO_INDEX; } - void BasePool::ClearIds() + void ComponentPool::ClearIds() { if (lastRemovedIndex == NO_INDEX) { @@ -438,14 +536,33 @@ namespace p idList.Clear(); } - void BasePool::BindOnPageAllocated() + void ComponentPool::BindOnPageAllocated() { idIndices.onPageAllocated = [](i32 index, i32* page, i32 size) { std::uninitialized_fill_n(page, size, NO_INDEX); }; } - i32 GetSmallestPool(TView pools) + TPool::TPool(p::EntityContext& ctx, Arena& arena) + : IPool(p::GetTypeId()), idRegistry{&ctx.GetIdRegistry()} + {} + + i32 TPool::Size() const + { + return idRegistry->GetDeferredRemovals().Size(); + } + + void TPool::Add(Id id, CRemoved) + { + idRegistry->DeferredRemove(id); + } + + const TArray& TPool::GetIdList() const + { + return idRegistry->GetDeferredRemovals(); + } + + i32 GetSmallestPool(TView pools) { sizet minSize = Limits::Max(); i32 minIndex = NO_INDEX; @@ -462,7 +579,7 @@ namespace p } - PoolInstance::PoolInstance(TypeId componentId, TUniquePtr&& pool) + PoolInstance::PoolInstance(TypeId componentId, TUniquePtr&& pool) : componentId{componentId}, pool{Move(pool)} {} PoolInstance::PoolInstance(PoolInstance&& other) noexcept @@ -502,7 +619,7 @@ namespace p return componentId; } - BasePool* PoolInstance::GetPool() const + IPool* PoolInstance::GetPool() const { return pool.Get(); } @@ -536,27 +653,9 @@ namespace p return *this; } - void EntityContext::Destroy(const Id id) - { - for (auto& pool : pools) - { - pool.GetPool()->Remove(id); - } - idRegistry.Destroy(id); - } - - void EntityContext::Destroy(TView ids) - { - for (auto& pool : pools) - { - pool.GetPool()->Remove(ids); - } - idRegistry.Destroy(ids); - } - void* EntityContext::AddDefault(TypeId typeId, Id id) { - if (BasePool* pool = GetPool(typeId)) + if (IPool* pool = GetPool(typeId)) { return pool->AddDefault(id); } @@ -565,20 +664,19 @@ namespace p void EntityContext::Remove(TypeId typeId, Id id) { - if (BasePool* pool = GetPool(typeId)) + if (IPool* pool = GetPool(typeId)) { pool->Remove(id); } } - BasePool* EntityContext::GetPool(TypeId componentId) const + IPool* EntityContext::GetPool(TypeId componentId) const { const i32 index = pools.FindSorted(PoolInstance{componentId, {}}); return index != NO_INDEX ? pools[index].GetPool() : nullptr; } - void EntityContext::GetPools( - TView componentIds, TArray& outPools) const + void EntityContext::GetPools(TView componentIds, TArray& outPools) const { for (const TypeId componentId : componentIds) { @@ -599,7 +697,6 @@ namespace p for (const PoolInstance& otherInstance : other.pools) { PoolInstance instance{otherInstance}; - instance.pool->SetOwnerContext(*this); pools.Add(Move(instance)); } @@ -611,11 +708,7 @@ namespace p { idRegistry = Move(other.idRegistry); pools = Move(other.pools); - for (auto& instance : pools) - { - instance.pool->SetOwnerContext(*this); - } - statics = Move(other.statics); + statics = Move(other.statics); // TODO: Move statics // TODO: Cache pools @@ -700,7 +793,7 @@ namespace p } - void ExcludeIdsWith(const BasePool* pool, TArray& ids, const bool shouldShrink) + void ExcludeIdsWith(const IPool* pool, TArray& ids, const bool shouldShrink) { for (i32 i = ids.Size() - 1; i >= 0; --i) { @@ -715,7 +808,7 @@ namespace p } } - void ExcludeIdsWithStable(const BasePool* pool, TArray& ids, const bool shouldShrink) + void ExcludeIdsWithStable(const IPool* pool, TArray& ids, const bool shouldShrink) { ids.RemoveIf( [pool](Id id) { @@ -724,7 +817,7 @@ namespace p shouldShrink); } - void ExcludeIdsWithout(const BasePool* pool, TArray& ids, const bool shouldShrink) + void ExcludeIdsWithout(const IPool* pool, TArray& ids, const bool shouldShrink) { for (i32 i = ids.Size() - 1; i >= 0; --i) { @@ -739,7 +832,7 @@ namespace p } } - void ExcludeIdsWithoutStable(const BasePool* pool, TArray& ids, const bool shouldShrink) + void ExcludeIdsWithoutStable(const IPool* pool, TArray& ids, const bool shouldShrink) { ids.RemoveIf( [pool](Id id) { @@ -748,7 +841,7 @@ namespace p shouldShrink); } - void FindIdsWith(const BasePool* pool, TView source, TArray& results) + void FindIdsWith(const IPool* pool, TView source, TArray& results) { if (pool) { @@ -762,8 +855,7 @@ namespace p } } } - void FindIdsWith( - TView pools, TView source, TArray& results) + void FindIdsWith(TView pools, TView source, TArray& results) { FindIdsWith(pools.First(), source, results); for (i32 i = 1; i < pools.Size(); ++i) @@ -772,7 +864,7 @@ namespace p } } - void FindIdsWithout(const BasePool* pool, TView source, TArray& results) + void FindIdsWithout(const IPool* pool, TView source, TArray& results) { if (pool) { @@ -793,7 +885,7 @@ namespace p } void ExtractIdsWith( - const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink) + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink) { results.ReserveMore(Min(i32(pool->Size()), source.Size())); for (i32 i = source.Size() - 1; i >= 0; --i) @@ -812,7 +904,7 @@ namespace p } void ExtractIdsWithStable( - const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink) + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink) { results.ReserveMore(Min(i32(pool->Size()), source.Size())); source.RemoveIf( @@ -828,7 +920,7 @@ namespace p } void ExtractIdsWithout( - const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink) + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink) { results.ReserveMore(source.Size()); for (i32 i = source.Size() - 1; i >= 0; --i) @@ -847,7 +939,7 @@ namespace p } void ExtractIdsWithoutStable( - const BasePool* pool, TArray& source, TArray& results, const bool shouldShrink) + const IPool* pool, TArray& source, TArray& results, const bool shouldShrink) { results.ReserveMore(Min(i32(pool->Size()), source.Size())); source.RemoveIf( @@ -862,14 +954,14 @@ namespace p shouldShrink); } - void FindAllIdsWith(TView pools, TArray& ids) + void FindAllIdsWith(TView pools, TArray& ids) { if (pools.IsEmpty()) { return; } - for (const BasePool* pool : pools) + for (const IPool* pool : pools) { if (!P_EnsureMsg(pool, "One of the pools is null. Is the access missing one or more of the " @@ -880,8 +972,8 @@ namespace p } } - const i32 smallestIdx = GetSmallestPool(pools); - const BasePool* iterablePool = pools[smallestIdx]; + const i32 smallestIdx = GetSmallestPool(pools); + const IPool* iterablePool = pools[smallestIdx]; ids.Clear(false); ids.Reserve(iterablePool->Size()); @@ -899,20 +991,20 @@ namespace p { if (i != smallestIdx) { - const BasePool* pool = pools[i]; + const IPool* pool = pools[i]; ExcludeIdsWithout(pool, ids, false); } } } - void FindAllIdsWithAny(TView pools, TArray& ids) + void FindAllIdsWithAny(TView pools, TArray& ids) { if (pools.IsEmpty()) { return; } - for (const BasePool* pool : pools) + for (const IPool* pool : pools) { if (!P_EnsureMsg(pool, "One of the pools is null. Is the access missing one or more of the " @@ -924,13 +1016,13 @@ namespace p } ids.Clear(); - for (const BasePool* pool : pools) + for (const IPool* pool : pools) { ids.Append(pool->begin(), pool->end()); } } - void FindAllIdsWithAnyUnique(TView pools, TArray& ids) + void FindAllIdsWithAnyUnique(TView pools, TArray& ids) { if (pools.IsEmpty()) { @@ -938,7 +1030,7 @@ namespace p } i32 maxPossibleSize = 0; - for (const BasePool* pool : pools) + for (const IPool* pool : pools) { if (!P_EnsureMsg(pool, "One of the pools is null. Is the access missing one or more of the " @@ -953,7 +1045,7 @@ namespace p TSet idsSet; idsSet.Reserve(maxPossibleSize); - for (const BasePool* pool : pools) + for (const IPool* pool : pools) { for (Id id : *pool) { @@ -997,15 +1089,40 @@ namespace p } - Id CreateId(EntityContext& ctx) + Id AddId(EntityContext& ctx) { return ctx.GetIdRegistry().Create(); } - void CreateIds(EntityContext& ctx, TView ids) + void AddId(EntityContext& ctx, TView ids) { ctx.GetIdRegistry().Create(ids); } + void RmId(EntityContext& ctx, TView ids, RmIdFlags flags) + { + TArray allIds; // Only used when removing children. Here for scope purposes. + if (HasFlag(flags, p::RmIdFlags::RemoveChildren)) + { + allIds.Append(ids); + GetAllIdChildren(ctx, ids, allIds); + // No children to detach since we will remove all of them + ids = allIds; + } + + if (HasFlag(flags, p::RmIdFlags::Instant)) + { + for (auto& pool : ctx.GetPools()) + { + pool.GetPool()->Remove(ids); + } + ctx.GetIdRegistry().Remove(ids); + } + else + { + ctx.GetIdRegistry().DeferredRemove(ids); + } + } + void AttachId( TAccessRef, TWrite> access, Id parent, TView children) @@ -1267,25 +1384,6 @@ namespace p } } - void RemoveId(TAccessRef, TWrite> access, TView ids, bool deep) - { - DetachIdParent(access, ids, true); - - if (deep) - { - TArray allIds; - allIds.Append(ids); - GetAllIdChildren(access, ids, allIds); - // No children to detach since we will remove all of them - access.GetContext().Destroy(allIds); - } - else - { - DetachIdChildren(access, ids); - access.GetContext().Destroy(ids); - } - } - bool FixParentIdLinks(TAccessRef, CParent> access, TView parents) { diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 0206bd46..460e2513 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -139,7 +139,7 @@ go_bandit([]() { Id id = ctx.Create(); ctx.Add(id); - ctx.Destroy(id); + RmId(ctx, id); AssertThat(ctx.IsValid(id), Is().False()); AssertThat(ctx.Has(id), Is().False()); @@ -216,7 +216,7 @@ go_bandit([]() { EntityContext ctx; Id id = ctx.Create(); ctx.Add(id); - ctx.Destroy(id); + p::RmId(ctx, id); AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.TryGet(id), Equals(nullptr)); @@ -228,7 +228,7 @@ go_bandit([]() { EntityContext ctx; Id id = ctx.Create(); ctx.Add(id); - ctx.Destroy(id); + RmId(ctx, id); id = ctx.Create(); ctx.Add(id); diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index 75b11d28..020903de 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -84,9 +84,22 @@ go_bandit([]() { it("Doesn't list removed ids", [&]() { TAccess access{ctx}; - ctx.Destroy(id2); // Remove first in the pool - ctx.Destroy(id3); // Remove last in the pool - ctx.Destroy(id4); // Remove last in the pool + RmId(ctx, id2, RmIdFlags::Instant); // Remove first in the pool + RmId(ctx, id3, RmIdFlags::Instant); // Remove last in the pool + RmId(ctx, id4, RmIdFlags::Instant); // Remove last in the pool + + TArray ids = FindAllIdsWith(access); + AssertThat(ids.Contains(NoId), Is().False()); + AssertThat(ids.Size(), Equals(1)); + }); + + it("Doesn't list (deferred) removed ids", [&]() { + TAccess access{ctx}; + RmId(ctx, id2); // Remove first in the pool + RmId(ctx, id3); // Remove last in the pool + RmId(ctx, id4); // Remove last in the pool + + ctx.FlushDeferredRemoves(); TArray ids = FindAllIdsWith(access); AssertThat(ids.Contains(NoId), Is().False()); From ea358c6e99e950f634c891f57287b24364a8f81e Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 20:37:57 +0100 Subject: [PATCH 02/14] [CICD] Updated build pipeline --- .github/workflows/build.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82d7c5e1..81c4ce4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,15 +19,15 @@ jobs: matrix: config: [Release, Debug] os: - - windows-2022 + - windows-2025 - ubuntu-24.04 - macos-15 compiler: - clang-18 - - gcc-14 + - gcc-15 - msvc exclude: - - os: windows-2022 + - os: windows-2025 compiler: gcc-14 - os: ubuntu-24.04 compiler: msvc @@ -46,21 +46,21 @@ jobs: - name: Setup Cpp uses: aminya/setup-cpp@v1 with: - # Skip compiler setup for macos clang-17 - compiler: ${{ (contains(matrix.os, 'macos') && matrix.compiler == 'clang-17') && '' || matrix.compiler }} + # Skip compiler setup for macos clang + compiler: ${{ (contains(matrix.os, 'macos') && contains(matrix.compiler, 'clang')) && '' || matrix.compiler }} vcvarsall: ${{ contains(matrix.os, 'windows') }} cmake: true ninja: true - - name: (MacOS) Install clang-17 through brew - if: contains(matrix.os, 'macos') && matrix.compiler == 'clang-17' + - name: (MacOS) Install clang through brew + if: contains(matrix.os, 'macos') && contains(matrix.compiler, 'clang') run: | - brew install llvm@17 - export LLVM_DIR="$(brew --prefix llvm@17)/lib/cmake" - echo "CC=$(brew --prefix llvm@17)/bin/clang" >> $GITHUB_ENV - echo "CXX=$(brew --prefix llvm@17)/bin/clang++" >> $GITHUB_ENV - echo "$(brew --prefix llvm@17)/bin" >> $GITHUB_PATH - brew link --overwrite llvm@17 + brew install llvm@18 + export LLVM_DIR="$(brew --prefix llvm@18)/lib/cmake" + echo "CC=$(brew --prefix llvm@18)/bin/clang" >> $GITHUB_ENV + echo "CXX=$(brew --prefix llvm@18)/bin/clang++" >> $GITHUB_ENV + echo "$(brew --prefix llvm@18)/bin" >> $GITHUB_PATH + brew link --overwrite llvm@18 - name: Cache Build uses: actions/cache@v4 From 914e2b03bf791fce58db5a970ec9515a69b95850 Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 20:38:04 +0100 Subject: [PATCH 03/14] Small fix --- Src/PipeECS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index 0abc4e81..999daf1f 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -102,7 +102,7 @@ namespace p } // Remaining entities - const int32 firstIndex = entities.Size(); + const i32 firstIndex = entities.Size(); for (i32 i = 0; i < newIds.Size(); ++i) { newIds[i] = MakeId(firstIndex + i, 0); From e0c89b446e79ddd8e19358c786ab8289712b04a4 Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 21:05:50 +0100 Subject: [PATCH 04/14] Added deferred removeal tests --- Include/Pipe/Core/Subprocess.h | 2 +- Tests/ECS/Access.spec.cpp | 2 +- Tests/ECS/Components.spec.cpp | 26 ++++++------- Tests/ECS/ECS.spec.cpp | 4 +- Tests/ECS/Filtering.spec.cpp | 10 ++--- Tests/ECS/IdRegistry.spec.cpp | 67 +++++++++++++++++++++++++++++----- 6 files changed, 80 insertions(+), 31 deletions(-) diff --git a/Include/Pipe/Core/Subprocess.h b/Include/Pipe/Core/Subprocess.h index bb3488f2..f5f76784 100644 --- a/Include/Pipe/Core/Subprocess.h +++ b/Include/Pipe/Core/Subprocess.h @@ -11,7 +11,7 @@ #include -#if !defined(_MSC_VER) +#if !defined(_WIN64) && !defined(_WIN32) #include #include #include diff --git a/Tests/ECS/Access.spec.cpp b/Tests/ECS/Access.spec.cpp index f42399ae..a4ef8efe 100644 --- a/Tests/ECS/Access.spec.cpp +++ b/Tests/ECS/Access.spec.cpp @@ -36,7 +36,7 @@ go_bandit([]() { AssertThat(access.Has(id), Is().False()); AssertThat(accessConst.Has(id), Is().False()); - id = ctx.Create(); + id = AddId(ctx); AssertThat(access.Has(id), Is().False()); AssertThat(accessConst.Has(id), Is().False()); diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 460e2513..b13b9a49 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -64,7 +64,7 @@ go_bandit([]() { describe("ECS.Components", []() { it("Can add one component", [&]() { EntityContext ctx; - Id id = ctx.Create(); + Id id = AddId(ctx); AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.TryGet(id), Equals(nullptr)); AssertThat(ctx.TryGet(id), Equals(nullptr)); @@ -80,7 +80,7 @@ go_bandit([]() { it("Can remove one component", [&]() { EntityContext ctx; - Id id = ctx.Create(); + Id id = AddId(ctx); ctx.Add(id); ctx.Remove(id); @@ -97,7 +97,7 @@ go_bandit([]() { it("Can add many components", [&]() { EntityContext ctx; TArray ids{3}; - ctx.Create(ids); + AddId(ctx, ids); ctx.AddN(ids, NonEmptyComponent{2}); for (Id id : ids) @@ -111,7 +111,7 @@ go_bandit([]() { it("Can remove many components", [&]() { EntityContext ctx; TArray ids{3}; - ctx.Create(ids); + AddId(ctx, ids); ctx.AddN(ids, NonEmptyComponent{2}); NonEmptyComponent::destructed = 0; @@ -136,7 +136,7 @@ go_bandit([]() { it("Components are removed after node is deleted", [&]() { EntityContext ctx; - Id id = ctx.Create(); + Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id); @@ -150,7 +150,7 @@ go_bandit([]() { it("Components keep state when added", [&]() { EntityContext ctx; - Id id = ctx.Create(); + Id id = AddId(ctx); ctx.AddN(id, NonEmptyComponent{2}); AssertThat(ctx.TryGet(id), !Equals(nullptr)); AssertThat(ctx.Get(id).a, Equals(2)); @@ -159,9 +159,9 @@ go_bandit([]() { it("Can copy registry", []() { EntityContext ctxa; - Id id = ctxa.Create(); + Id id = AddId(ctxa); ctxa.Add(id); - Id id2 = ctxa.Create(); + Id id2 = AddId(ctxa); ctxa.AddN(id2, NonEmptyComponent{2}); EntityContext ctxb{ctxa}; @@ -179,7 +179,7 @@ go_bandit([]() { AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.Has(id), Is().False()); - id = ctx.Create(); + id = AddId(ctx); AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.Has(id), Is().False()); @@ -194,7 +194,7 @@ go_bandit([]() { EntityContext ctx; TArray ids{3}; - ctx.Create(ids); + AddId(ctx, ids); ctx.AddN(ids, NonEmptyComponent{2}); ctx.AddN(ids); @@ -214,7 +214,7 @@ go_bandit([]() { it("Components are removed with the entity", [&]() { EntityContext ctx; - Id id = ctx.Create(); + Id id = AddId(ctx); ctx.Add(id); p::RmId(ctx, id); @@ -226,11 +226,11 @@ go_bandit([]() { it("Can access components on recicled entities", [&]() { EntityContext ctx; - Id id = ctx.Create(); + Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id); - id = ctx.Create(); + id = AddId(ctx); ctx.Add(id); AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.Has(id), Is().True()); diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index f0961301..625bcafd 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -24,7 +24,7 @@ go_bandit([]() { static bool calledAdd; EntityContext origin; - Id id = origin.Create(); + Id id = AddId(origin); calledAdd = false; astPtr = &origin; @@ -66,7 +66,7 @@ go_bandit([]() { static bool calledAdd; EntityContext origin; - Id id = origin.Create(); + Id id = AddId(origin); calledAdd = false; astPtr = &origin; diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index 020903de..a3de8ed9 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -43,11 +43,11 @@ go_bandit([]() { describe("ECS.Filtering", [&]() { before_each([&]() { ctx = {}; - id1 = ctx.Create(); - id2 = ctx.Create(); - id3 = ctx.Create(); - id4 = ctx.Create(); - id5 = ctx.Create(); + id1 = AddId(ctx); + id2 = AddId(ctx); + id3 = AddId(ctx); + id4 = AddId(ctx); + id5 = AddId(ctx); ctx.Add(id1); ctx.Add(id2); ctx.Add(id3); diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index d05da4bd..93d9a823 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -29,9 +29,8 @@ go_bandit([]() { it("Can create one id", [&]() { IdRegistry ids; AssertThat(ids.Size(), Equals(0)); - Id id = ids.Create(); - AssertThat(id, !Equals(Id(NoId))); + AssertThat(id, !Equals(NoId)); AssertThat(ids.IsValid(id), Is().True()); AssertThat(ids.Size(), Equals(1)); }); @@ -40,33 +39,83 @@ go_bandit([]() { IdRegistry ids; Id id = ids.Create(); AssertThat(ids.Size(), Equals(1)); - - AssertThat(ids.Destroy(id), Is().True()); + AssertThat(ids.Remove(id), Is().True()); AssertThat(ids.IsValid(id), Is().False()); AssertThat(ids.Size(), Equals(0)); }); it("Can create two and remove first", [&]() { IdRegistry ids; - Id id1 = ids.Create(); ids.Create(); - AssertThat(ids.Destroy(id1), Is().True()); + AssertThat(ids.Remove(id1), Is().True()); AssertThat(ids.IsValid(id1), Is().False()); AssertThat(ids.Size(), Equals(1)); }); it("Can create two and remove last", [&]() { IdRegistry ids; - ids.Create(); Id id2 = ids.Create(); + AssertThat(ids.Remove(id2), Is().True()); + AssertThat(ids.IsValid(id2), Is().False()); + AssertThat(ids.Size(), Equals(1)); + }); + + it("Can remove one id (deferred)", [&]() { + IdRegistry ids; + Id id = ids.Create(); + AssertThat(ids.Size(), Equals(1)); + AssertThat(ids.DeferredRemove(id), Is().True()); + AssertThat(ids.IsValid(id), Is().False()); + AssertThat(ids.Size(), Equals(0)); + }); + + it("Can create two and remove first (deferred)", [&]() { + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + AssertThat(ids.DeferredRemove(id1), Is().True()); + AssertThat(ids.IsValid(id1), Is().False()); + AssertThat(ids.Size(), Equals(1)); + }); - AssertThat(ids.Destroy(id2), Is().True()); + it("Can create two and remove last (deferred)", [&]() { + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + AssertThat(ids.DeferredRemove(id2), Is().True()); AssertThat(ids.IsValid(id2), Is().False()); AssertThat(ids.Size(), Equals(1)); }); + it("Removed id index gets reused", [&]() { + IdRegistry ids; + ids.Create(1); + Id id = ids.Create(); + ids.Create(1); + AssertThat(ids.Remove(id), Is().True()); + Id id2 = ids.Create(); + AssertThat(id2.GetIndex(), Equals(id.GetIndex())); + Id id3 = ids.Create(); + AssertThat(id3.GetIndex(), !Equals(id.GetIndex())); + }); + + it("Deferred removed id index doesn't get reused until flushed", [&]() { + IdRegistry ids; + ids.Create(1); + Id id = ids.Create(); + ids.Create(1); + AssertThat(ids.DeferredRemove(id), Is().True()); + Id id2 = ids.Create(); + AssertThat(id2.GetIndex(), !Equals(id.GetIndex())); + ids.FlushDeferredRemoves(); + Id id3 = ids.Create(); + AssertThat(id3.GetIndex(), Equals(id.GetIndex())); + Id id4 = ids.Create(); + AssertThat(id4.GetIndex(), !Equals(id.GetIndex())); + }); + it("Can create many ids", [&]() { IdRegistry ids; AssertThat(ids.Size(), Equals(0)); @@ -88,7 +137,7 @@ go_bandit([]() { ids.Create(list); AssertThat(ids.Size(), Equals(3)); - AssertThat(ids.Destroy(list), Is().True()); + AssertThat(ids.Remove(list), Is().True()); AssertThat(ids.Size(), Equals(0)); for (i32 i = 0; i < list.Size(); ++i) From 5e2dbeec3e8ed75bfa57647632dbffacbf74e0db Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 21:06:13 +0100 Subject: [PATCH 05/14] Added deferred removal tests (2) --- Tests/ECS/IdRegistry.spec.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 93d9a823..22e80735 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -126,7 +126,7 @@ go_bandit([]() { AssertThat(ids.Size(), Equals(3)); for (i32 i = 0; i < list.Size(); ++i) { - AssertThat(list[i], Equals(Id(i))); + AssertThat(list[i].GetIndex(), Equals(i)); AssertThat(ids.IsValid(list[i]), Is().True()); } }); @@ -145,5 +145,20 @@ go_bandit([]() { AssertThat(ids.IsValid(list[i]), Is().False()); } }); + + it("Can remove many ids (deferred)", [&]() { + IdRegistry ids; + TArray list(3); + ids.Create(list); + AssertThat(ids.Size(), Equals(3)); + + AssertThat(ids.DeferredRemove(list), Is().True()); + AssertThat(ids.Size(), Equals(0)); + + for (i32 i = 0; i < list.Size(); ++i) + { + AssertThat(ids.IsValid(list[i]), Is().False()); + } + }); }); }); From a283281ec4df7589b8f67ca071f05193a73dd487 Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 21:18:11 +0100 Subject: [PATCH 06/14] Test fixes --- Include/PipeECS.h | 5 +++-- Src/PipeECS.cpp | 18 ++++++++++++++---- Tests/ECS/Filtering.spec.cpp | 2 +- Tests/ECS/IdRegistry.spec.cpp | 10 +++++----- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Include/PipeECS.h b/Include/PipeECS.h index f28748b4..e1c9ef74 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -251,7 +251,7 @@ namespace p void Create(TView newIds); bool Remove(TView ids); bool DeferredRemove(TView ids); - void FlushDeferredRemoves(); + void FlushDeferredRemovals(); const TArray& GetDeferredRemovals() const { return deferredRemoves; @@ -2207,7 +2207,8 @@ namespace p P_API void AddId(EntityContext& ctx, TView Ids); // Remove - P_API void RmId(EntityContext& ctx, TView ids, RmIdFlags flags = RmIdFlags::None); + P_API bool RmId(EntityContext& ctx, TView ids, RmIdFlags flags = RmIdFlags::None); + P_API bool FlushDeferredRemovals(EntityContext& ctx); #pragma endregion Editing diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index 999daf1f..d120fe20 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -154,7 +154,7 @@ namespace p return (deferredRemoves.Size() - lastPending) > 0; } - void IdRegistry::FlushDeferredRemoves() + void IdRegistry::FlushDeferredRemovals() { available.ReserveMore(deferredRemoves.Size()); for (Id id : deferredRemoves) @@ -1098,7 +1098,7 @@ namespace p ctx.GetIdRegistry().Create(ids); } - void RmId(EntityContext& ctx, TView ids, RmIdFlags flags) + bool RmId(EntityContext& ctx, TView ids, RmIdFlags flags) { TArray allIds; // Only used when removing children. Here for scope purposes. if (HasFlag(flags, p::RmIdFlags::RemoveChildren)) @@ -1115,14 +1115,24 @@ namespace p { pool.GetPool()->Remove(ids); } - ctx.GetIdRegistry().Remove(ids); + return ctx.GetIdRegistry().Remove(ids); } else { - ctx.GetIdRegistry().DeferredRemove(ids); + return ctx.GetIdRegistry().DeferredRemove(ids); } } + bool FlushDeferredRemovals(EntityContext& ctx) + { + TView ids = ctx.GetIdRegistry().GetDeferredRemovals(); + for (auto& pool : ctx.GetPools()) + { + pool.GetPool()->Remove(ids); + } + ctx.GetIdRegistry().FlushDeferredRemovals(); + } + void AttachId( TAccessRef, TWrite> access, Id parent, TView children) diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index a3de8ed9..f7d6bac3 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -99,7 +99,7 @@ go_bandit([]() { RmId(ctx, id3); // Remove last in the pool RmId(ctx, id4); // Remove last in the pool - ctx.FlushDeferredRemoves(); + FlushDeferredRemovals(ctx); TArray ids = FindAllIdsWith(access); AssertThat(ids.Contains(NoId), Is().False()); diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 22e80735..939d973d 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -91,9 +91,9 @@ go_bandit([]() { it("Removed id index gets reused", [&]() { IdRegistry ids; - ids.Create(1); + ids.Create(); Id id = ids.Create(); - ids.Create(1); + ids.Create(); AssertThat(ids.Remove(id), Is().True()); Id id2 = ids.Create(); AssertThat(id2.GetIndex(), Equals(id.GetIndex())); @@ -103,13 +103,13 @@ go_bandit([]() { it("Deferred removed id index doesn't get reused until flushed", [&]() { IdRegistry ids; - ids.Create(1); + ids.Create(); Id id = ids.Create(); - ids.Create(1); + ids.Create(); AssertThat(ids.DeferredRemove(id), Is().True()); Id id2 = ids.Create(); AssertThat(id2.GetIndex(), !Equals(id.GetIndex())); - ids.FlushDeferredRemoves(); + ids.FlushDeferredRemovals(); Id id3 = ids.Create(); AssertThat(id3.GetIndex(), Equals(id.GetIndex())); Id id4 = ids.Create(); From 491401249b4eb46a8ff61d5f38242ab22f165f6c Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 21:25:26 +0100 Subject: [PATCH 07/14] Subprocess fixes for Windows GCC --- Include/Pipe/Core/Subprocess.h | 13 ----------- Src/Core/Subprocess.cpp | 42 ++++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Include/Pipe/Core/Subprocess.h b/Include/Pipe/Core/Subprocess.h index f5f76784..b9f45437 100644 --- a/Include/Pipe/Core/Subprocess.h +++ b/Include/Pipe/Core/Subprocess.h @@ -7,19 +7,6 @@ #include "Pipe/Export.h" #include "PipeArrays.h" -#include -#include - - -#if !defined(_WIN64) && !defined(_WIN32) - #include - #include - #include - #include - #include - #include -#endif - namespace p { diff --git a/Src/Core/Subprocess.cpp b/Src/Core/Subprocess.cpp index 1969512b..d5089adc 100644 --- a/Src/Core/Subprocess.cpp +++ b/Src/Core/Subprocess.cpp @@ -8,18 +8,36 @@ #include "PipeArrays.h" #include "PipePlatform.h" +#include +#include -#if defined(_MSC_VER) - #include + +#ifndef P_PLATFORM_WINDOWS + #if defined(_WIN64) || defined(_WIN32) + #define P_PLATFORM_WINDOWS 1 + #else + #define P_PLATFORM_WINDOWS 0 + #endif +#endif + +#if P_PLATFORM_WINDOWS #include + #include #else + #include + #include + #include + #include + #include + #include + extern char** environ; #endif namespace p { -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS struct SubprocessInfo { void* hProcess; @@ -86,7 +104,7 @@ namespace p p::Swap(cinFile, other.cinFile); p::Swap(coutFile, other.coutFile); p::Swap(cerrFile, other.cerrFile); -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS p::Swap(hProcess, other.hProcess); p::Swap(hStdInput, other.hStdInput); p::Swap(hEventOutput, other.hEventOutput); @@ -109,7 +127,7 @@ namespace p } -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS const char* GetSystemErrorMessage(char* buffer, i32 size, i32 error) { P_Check(buffer && size); @@ -189,7 +207,7 @@ namespace p Subprocess instance; instance.options = options; -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS i32 i, j; u64 flags = 0; static constexpr u64 startFUseStdHandles = 0x00000100; @@ -585,7 +603,7 @@ namespace p i32 WaitProcess(Subprocess* process, i32* outReturnCode) { -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS const u64 infinite = 0xFFFFFFFF; if (process->cinFile) @@ -672,7 +690,7 @@ namespace p process->cerrFile = nullptr; } -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS if (process->hProcess) { CloseHandle(process->hProcess); @@ -700,7 +718,7 @@ namespace p i32 TerminateProcess(Subprocess* process) { -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS if (process->hProcess) { u32 killed_process_exit_code; @@ -727,7 +745,7 @@ namespace p unsigned ReadProcessCout(Subprocess* process, char* buffer, u32 size) { -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS void* handle; u64 bytesRead = 0; SubprocessOverlapped overlapped; @@ -775,7 +793,7 @@ namespace p unsigned ReadProcessCerr(Subprocess* process, char* buffer, unsigned size) { -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS void* handle; u64 bytesRead = 0; SubprocessOverlapped overlapped; @@ -828,7 +846,7 @@ namespace p return false; } -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS constexpr u64 zero = 0x0; constexpr u64 wait_object_0 = 0x00000000L; From 13a7ae241fdb5ca943b6da48796cfa39d3f2a965 Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 21:25:35 +0100 Subject: [PATCH 08/14] More test fixes --- Tests/ECS/ECS.spec.cpp | 2 +- Tests/ECS/IdRegistry.spec.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 625bcafd..be45a2de 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -104,7 +104,7 @@ go_bandit([]() { it("Can assure pool", [&]() { EntityContext origin; TPool& pool = origin.AssurePool(); - AssertThat(&origin, Equals(&pool.GetContext())); + AssertThat(pool.Size(), Equals(0)); }); }); }); diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 939d973d..1ccecb9a 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -17,7 +17,7 @@ namespace snowhouse static std::string ToString(Id id) { std::stringstream stream; - stream << "Id(" << UnderlyingType(id) << ")"; + stream << "Id(" << id.value << ")"; return stream.str(); } }; From e9d6b436c5adc0ec40067f6f9f696ae6112e3efa Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 4 Feb 2026 21:38:47 +0100 Subject: [PATCH 09/14] More test fixes --- Include/Pipe/Core/Subprocess.h | 11 +++++++++- Include/PipeECS.h | 2 +- Src/Core/Subprocess.cpp | 8 ------- Tests/ECS/Components.spec.cpp | 2 +- Tests/ECS/ECS.spec.cpp | 40 ++++++++++++++++------------------ Tests/ECS/Filtering.spec.cpp | 2 +- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/Include/Pipe/Core/Subprocess.h b/Include/Pipe/Core/Subprocess.h index b9f45437..f3ee5542 100644 --- a/Include/Pipe/Core/Subprocess.h +++ b/Include/Pipe/Core/Subprocess.h @@ -7,6 +7,15 @@ #include "Pipe/Export.h" #include "PipeArrays.h" +// Including PipePlatform.h would be overkill just for this +#ifndef P_PLATFORM_WINDOWS + #if defined(_WIN64) || defined(_WIN32) + #define P_PLATFORM_WINDOWS 1 + #else + #define P_PLATFORM_WINDOWS 0 + #endif +#endif + namespace p { @@ -48,7 +57,7 @@ namespace p FILE* coutFile = nullptr; FILE* cerrFile = nullptr; -#if defined(_MSC_VER) +#if P_PLATFORM_WINDOWS void* hProcess = nullptr; void* hStdInput = nullptr; void* hEventOutput = nullptr; diff --git a/Include/PipeECS.h b/Include/PipeECS.h index e1c9ef74..dc697fab 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -251,7 +251,7 @@ namespace p void Create(TView newIds); bool Remove(TView ids); bool DeferredRemove(TView ids); - void FlushDeferredRemovals(); + bool FlushDeferredRemovals(); const TArray& GetDeferredRemovals() const { return deferredRemoves; diff --git a/Src/Core/Subprocess.cpp b/Src/Core/Subprocess.cpp index d5089adc..beef687a 100644 --- a/Src/Core/Subprocess.cpp +++ b/Src/Core/Subprocess.cpp @@ -12,14 +12,6 @@ #include -#ifndef P_PLATFORM_WINDOWS - #if defined(_WIN64) || defined(_WIN32) - #define P_PLATFORM_WINDOWS 1 - #else - #define P_PLATFORM_WINDOWS 0 - #endif -#endif - #if P_PLATFORM_WINDOWS #include #include diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index b13b9a49..c314a9c4 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -17,7 +17,7 @@ namespace snowhouse static std::string ToString(Id id) { std::stringstream stream; - stream << "Id(" << UnderlyingType(id) << ")"; + stream << "Id(" << id.value << ")"; return stream.str(); } }; diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index be45a2de..42b34f47 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -20,20 +20,20 @@ struct ATypeB go_bandit([]() { describe("ECS", []() { it("Can copy tree", [&]() { - static EntityContext* astPtr = nullptr; + static EntityContext* ctxPtr = nullptr; static bool calledAdd; EntityContext origin; Id id = AddId(origin); calledAdd = false; - astPtr = &origin; - origin.OnAdd().Bind([](EntityContext& ast, auto ids) { + ctxPtr = &origin; + origin.OnAdd().Bind([&origin](auto ids) { for (Id id : ids) { - AssertThat(ast.Has(id), Equals(true)); + AssertThat(origin.Has(id), Equals(true)); } - AssertThat(astPtr, Equals(&ast)); + AssertThat(ctxPtr, Equals(&origin)); calledAdd = true; }); origin.Add(id); @@ -42,40 +42,38 @@ go_bandit([]() { EntityContext target{origin}; AssertThat(origin.IsValid(id), Equals(true)); AssertThat(origin.Has(id), Equals(true)); - AssertThat(target.IsValid(id), Equals(true)); AssertThat(target.Has(id), Equals(true)); calledAdd = false; - astPtr = ⌖ - target.OnAdd().Bind([](EntityContext& ast, auto ids) { + ctxPtr = ⌖ + target.OnAdd().Bind([&target](auto ids) { for (Id id : ids) { - AssertThat(ast.Has(id), Equals(true)); + AssertThat(target.Has(id), Equals(true)); } - AssertThat(astPtr, Equals(&ast)); + AssertThat(ctxPtr, Equals(&target)); calledAdd = true; }); - target.Add(id); AssertThat(calledAdd, Equals(true)); }); it("Can move tree", [&]() { - static EntityContext* astPtr = nullptr; + static EntityContext* ctxPtr = nullptr; static bool calledAdd; EntityContext origin; - Id id = AddId(origin); + Id id = AddId(ctx); calledAdd = false; - astPtr = &origin; - origin.OnAdd().Bind([](EntityContext& ast, auto ids) { + ctxPtr = &origin; + origin.OnAdd().Bind([&origin](auto ids) { for (Id id : ids) { - AssertThat(ast.Has(id), Equals(true)); + AssertThat(origin.Has(id), Equals(true)); } - AssertThat(astPtr, Equals(&ast)); + AssertThat(ctxPtr, Equals(&origin)); calledAdd = true; }); origin.Add(id); @@ -88,13 +86,13 @@ go_bandit([]() { AssertThat(target.Has(id), Equals(true)); calledAdd = false; - astPtr = ⌖ - target.OnAdd().Bind([](EntityContext& ast, auto ids) { + ctxPtr = ⌖ + target.OnAdd().Bind([&target](auto ids) { for (Id id : ids) { - AssertThat(ast.Has(id), Equals(true)); + AssertThat(target.Has(id), Equals(true)); } - AssertThat(astPtr, Equals(&ast)); + AssertThat(ctxPtr, Equals(&target)); calledAdd = true; }); target.Add(id); diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index f7d6bac3..5b402e5c 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -18,7 +18,7 @@ namespace snowhouse static std::string ToString(Id id) { std::stringstream stream; - stream << "Id(" << UnderlyingType(id) << ")"; + stream << "Id(" << id.value << ")"; return stream.str(); } }; From d2f78a46e4da03f8286c9688c2d56d7fe18ee6ac Mon Sep 17 00:00:00 2001 From: muit Date: Thu, 5 Feb 2026 00:04:54 +0100 Subject: [PATCH 10/14] More test fixes --- Include/PipeECS.h | 5 +-- Src/PipeECS.cpp | 22 ++++++++---- Tests/ECS/Components.spec.cpp | 42 ++++++++++++++++++++++- Tests/ECS/ECS.spec.cpp | 2 +- Tests/Reflection/MacroReflection.spec.cpp | 18 +++++----- Tests/Reflection/Object.spec.cpp | 2 +- Tests/Serialization/Json.spec.cpp | 7 ++-- 7 files changed, 72 insertions(+), 26 deletions(-) diff --git a/Include/PipeECS.h b/Include/PipeECS.h index dc697fab..c77aec47 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -260,10 +260,7 @@ namespace p bool WasRemoved(Id id) const; TOptional GetValidVersion(Index idx) const; - u32 Size() const - { - return entities.Size() - available.Size(); - } + u32 Size() const; template void Each(Callback cb) const; diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index d120fe20..cf4852d8 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -154,14 +154,19 @@ namespace p return (deferredRemoves.Size() - lastPending) > 0; } - void IdRegistry::FlushDeferredRemovals() + bool IdRegistry::FlushDeferredRemovals() { - available.ReserveMore(deferredRemoves.Size()); - for (Id id : deferredRemoves) + if (deferredRemoves.Size() > 0) { - available.Add(id.GetIndex()); + available.ReserveMore(deferredRemoves.Size()); + for (Id id : deferredRemoves) + { + available.Add(id.GetIndex()); + } + deferredRemoves.Clear(); + return true; } - deferredRemoves.Clear(); + return false; } bool IdRegistry::IsValid(Id id) const @@ -189,6 +194,11 @@ namespace p return TOptional{}; } + u32 IdRegistry::Size() const + { + return entities.Size() - available.Size() - deferredRemoves.Size(); + } + void EntityReader::SerializeEntities( TArray& entities, TFunction onReadPools) @@ -1130,7 +1140,7 @@ namespace p { pool.GetPool()->Remove(ids); } - ctx.GetIdRegistry().FlushDeferredRemovals(); + return ctx.GetIdRegistry().FlushDeferredRemovals(); } diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index c314a9c4..104d13b9 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -139,9 +139,29 @@ go_bandit([]() { Id id = AddId(ctx); ctx.Add(id); + RmId(ctx, id, p::RmIdFlags::Instant); + AssertThat(ctx.IsValid(id), Is().False()); + + AssertThat(ctx.Has(id), Is().False()); + AssertThat(ctx.TryGet(id), Equals(nullptr)); + AssertThat(ctx.Has(id), Is().False()); + AssertThat(ctx.TryGet(id), Equals(nullptr)); + }); + + it("Components are removed after node is deleted (deferred)", [&]() { + EntityContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); AssertThat(ctx.IsValid(id), Is().False()); + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.TryGet(id), Equals(nullptr)); + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.TryGet(id), !Equals(nullptr)); + + FlushDeferredRemovals(ctx); AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.TryGet(id), Equals(nullptr)); AssertThat(ctx.Has(id), Is().False()); @@ -216,8 +236,28 @@ go_bandit([]() { EntityContext ctx; Id id = AddId(ctx); ctx.Add(id); - p::RmId(ctx, id); + RmId(ctx, id, p::RmIdFlags::Instant); + AssertThat(ctx.IsValid(id), Is().False()); + + AssertThat(ctx.Has(id), Is().False()); + AssertThat(ctx.TryGet(id), Equals(nullptr)); + AssertThat(ctx.Has(id), Is().False()); + AssertThat(ctx.TryGet(id), Equals(nullptr)); + }); + + it("Components are removed with the entity (deferred)", [&]() { + EntityContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + AssertThat(ctx.IsValid(id), Is().False()); + + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.TryGet(id), Equals(nullptr)); + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.TryGet(id), !Equals(nullptr)); + FlushDeferredRemovals(ctx); AssertThat(ctx.Has(id), Is().False()); AssertThat(ctx.TryGet(id), Equals(nullptr)); AssertThat(ctx.Has(id), Is().False()); diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 42b34f47..9eec9d05 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -64,7 +64,7 @@ go_bandit([]() { static bool calledAdd; EntityContext origin; - Id id = AddId(ctx); + Id id = AddId(origin); calledAdd = false; ctxPtr = &origin; diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index 071da7dd..19ce4336 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -23,16 +23,18 @@ struct TestStruct go_bandit([]() { describe("Reflection.Macros", []() { - p::TypeId testStructType = p::RegisterTypeId(); + it("Can get property names", [&]() { + p::TypeId testStructType = p::RegisterTypeId(); - AssertThat(p::HasTypeFlags(testStructType, p::TF_Struct), Equals(true)); + AssertThat(p::HasTypeFlags(testStructType, p::TF_Struct), Equals(true)); - auto properties = p::GetTypeProperties(testStructType); - AssertThat(properties.Size(), Equals(2)); + auto properties = p::GetTypeProperties(testStructType); + AssertThat(properties.Size(), Equals(2)); - // AssertThat(properties[0].typeId, Equals(p::GetTypeId>())); - AssertThat(properties[0]->name.Data(), Equals("value0")); - // AssertThat(properties[1].typeId, Equals(p::GetTypeId())); - AssertThat(properties[1]->name.Data(), Equals("value1")); + // AssertThat(properties[0].typeId, Equals(p::GetTypeId>())); + AssertThat(properties[0]->name.Data(), Equals("value0")); + // AssertThat(properties[1].typeId, Equals(p::GetTypeId())); + AssertThat(properties[1]->name.Data(), Equals("value1")); + }); }); }); diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index b48c41f8..20aab755 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -38,7 +38,7 @@ go_bandit([]() { auto owner2 = p::MakeOwned(owner); AssertThat(owner2->bConstructed, Equals(true)); - AssertThat(owner2->GetOwner().IsValid(), Equals(true)); + AssertThat(owner2->GetOwner().Get(), Equals(owner.Get())); }); }); }); diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp index 67109c0b..4e54781d 100644 --- a/Tests/Serialization/Json.spec.cpp +++ b/Tests/Serialization/Json.spec.cpp @@ -13,11 +13,8 @@ go_bandit([]() { describe("Serialization.Json", []() { describe("Reader", [&]() { it("Can create a reader", [&]() { - JsonFormatReader reader{""}; - AssertThat(reader.IsValid(), Equals(false)); - - JsonFormatReader reader2{"{}"}; - AssertThat(reader2.IsValid(), Equals(true)); + JsonFormatReader reader{"{}"}; + AssertThat(reader.IsValid(), Is().True()); }); it("Can read from object value", [&]() { From 730f3068bdcc098ed991e995506f82d13cfded25 Mon Sep 17 00:00:00 2001 From: muit Date: Thu, 5 Feb 2026 10:53:39 +0100 Subject: [PATCH 11/14] Updated utf8, castable memory arenas --- Include/Pipe/Core/TypeId.h | 8 +- Include/Pipe/Extern/utf8.h | 10 +- Include/Pipe/Extern/utf8/checked.h | 691 +++++++++++++-------------- Include/Pipe/Extern/utf8/core.h | 60 ++- Include/Pipe/Extern/utf8/cpp11.h | 63 +-- Include/Pipe/Extern/utf8/cpp17.h | 126 ++--- Include/Pipe/Extern/utf8/cpp20.h | 182 +++---- Include/Pipe/Extern/utf8/unchecked.h | 482 +++++++++---------- Include/PipeMemory.h | 15 +- Include/PipeMemoryArenas.h | 36 ++ Include/PipeReflect.h | 6 +- Include/PipeSerialize.h | 4 +- Src/Memory/MemoryStats.cpp | 6 + 13 files changed, 847 insertions(+), 842 deletions(-) diff --git a/Include/Pipe/Core/TypeId.h b/Include/Pipe/Core/TypeId.h index 972f015b..2ab01179 100644 --- a/Include/Pipe/Core/TypeId.h +++ b/Include/Pipe/Core/TypeId.h @@ -105,8 +105,8 @@ namespace p } -#pragma region Casteable - struct Casteable +#pragma region Castable + struct Castable { private: mutable TypeId typeId; @@ -126,8 +126,8 @@ namespace p }; template - concept IsCasteable = Derived, Casteable, false>; -#pragma endregion Casteable + concept IsCastable = Derived, Castable, false>; +#pragma endregion Castable } // namespace p diff --git a/Include/Pipe/Extern/utf8.h b/Include/Pipe/Extern/utf8.h index 6d5c1c9d..b5135309 100644 --- a/Include/Pipe/Extern/utf8.h +++ b/Include/Pipe/Extern/utf8.h @@ -34,13 +34,13 @@ and set it to one of the values used by the __cplusplus predefined macro. For instance, #define UTF_CPP_CPLUSPLUS 199711L -will cause the UTF-8 CPP library to use only types and language features available in the C++ 98 -standard. Some library features will be disabled. +will cause the UTF-8 CPP library to use only types and language features available in the C++ 98 standard. +Some library features will be disabled. If you leave UTF_CPP_CPLUSPLUS undefined, it will be internally assigned to __cplusplus. */ -#include "Pipe/Extern/utf8/checked.h" -#include "Pipe/Extern/utf8/unchecked.h" +#include "utf8/checked.h" +#include "utf8/unchecked.h" -#endif // header guard +#endif // header guard diff --git a/Include/Pipe/Extern/utf8/checked.h b/Include/Pipe/Extern/utf8/checked.h index 4b159fa1..96ceb4d5 100644 --- a/Include/Pipe/Extern/utf8/checked.h +++ b/Include/Pipe/Extern/utf8/checked.h @@ -28,373 +28,332 @@ DEALINGS IN THE SOFTWARE. #ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#include "Pipe/Extern/utf8/core.h" - +#include "core.h" #include - namespace utf8 { - // Base for the exceptions that may be thrown from the library - class exception : public ::std::exception - {}; - - // Exceptions that may be thrown from the library functions. - class invalid_code_point : public exception - { - utfchar32_t cp; - - public: - invalid_code_point(utfchar32_t codepoint) : cp(codepoint) {} - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE - { - return "Invalid code point"; - } - utfchar32_t code_point() const - { - return cp; - } - }; - - class invalid_utf8 : public exception - { - utfchar8_t u8; - - public: - invalid_utf8(utfchar8_t u) : u8(u) {} - invalid_utf8(char c) : u8(static_cast(c)) {} - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE - { - return "Invalid UTF-8"; - } - utfchar8_t utf8_octet() const - { - return u8; - } - }; - - class invalid_utf16 : public exception - { - utfchar16_t u16; - - public: - invalid_utf16(utfchar16_t u) : u16(u) {} - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE - { - return "Invalid UTF-16"; - } - utfchar16_t utf16_word() const - { - return u16; - } - }; - - class not_enough_room : public exception - { - public: - virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE - { - return "Not enough space"; - } - }; - - /// The library API - functions intended to be called by the users - - template - octet_iterator append(utfchar32_t cp, octet_iterator result) - { - if (!utf8::internal::is_code_point_valid(cp)) - throw invalid_code_point(cp); - - return internal::append(cp, result); - } - - inline void append(utfchar32_t cp, std::string& s) - { - append(cp, std::back_inserter(s)); - } - - template - word_iterator append16(utfchar32_t cp, word_iterator result) - { - if (!utf8::internal::is_code_point_valid(cp)) - throw invalid_code_point(cp); - - return internal::append16(cp, result); - } - - template - output_iterator replace_invalid( - octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) - { - while (start != end) - { - octet_iterator sequence_start = start; - internal::utf_error err_code = utf8::internal::validate_next(start, end); - switch (err_code) - { - case internal::UTF8_OK: - for (octet_iterator it = sequence_start; it != start; ++it) - *out++ = *it; - break; - case internal::NOT_ENOUGH_ROOM: - out = utf8::append(replacement, out); - start = end; - break; - case internal::INVALID_LEAD: - out = utf8::append(replacement, out); - ++start; - break; - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: - case internal::INVALID_CODE_POINT: - out = utf8::append(replacement, out); - ++start; - // just one replacement mark for the sequence - while (start != end && utf8::internal::is_trail(*start)) - ++start; - break; - } - } - return out; - } - - template - inline output_iterator replace_invalid( - octet_iterator start, octet_iterator end, output_iterator out) - { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); - return utf8::replace_invalid(start, end, out, replacement_marker); - } - - inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } - - inline std::string replace_invalid(const std::string& s) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - template - utfchar32_t next(octet_iterator& it, octet_iterator end) - { - utfchar32_t cp = 0; - internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); - switch (err_code) - { - case internal::UTF8_OK: break; - case internal::NOT_ENOUGH_ROOM: throw not_enough_room(); - case internal::INVALID_LEAD: - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: throw invalid_utf8(static_cast(*it)); - case internal::INVALID_CODE_POINT: throw invalid_code_point(cp); - } - return cp; - } - - template - utfchar32_t next16(word_iterator& it, word_iterator end) - { - utfchar32_t cp = 0; - internal::utf_error err_code = utf8::internal::validate_next16(it, end, cp); - if (err_code == internal::NOT_ENOUGH_ROOM) - throw not_enough_room(); - return cp; - } - - template - utfchar32_t peek_next(octet_iterator it, octet_iterator end) - { - return utf8::next(it, end); - } - - template - utfchar32_t prior(octet_iterator& it, octet_iterator start) - { - // can't do much if it == start - if (it == start) - throw not_enough_room(); - - octet_iterator end = it; - // Go back until we hit either a lead octet or start - while (utf8::internal::is_trail(*(--it))) - if (it == start) - throw invalid_utf8(*it); // error - no lead byte in the sequence - return utf8::peek_next(it, end); - } - - template - void advance(octet_iterator& it, distance_type n, octet_iterator end) - { - const distance_type zero(0); - if (n < zero) - { - // backward - for (distance_type i = n; i < zero; ++i) - utf8::prior(it, end); - } - else - { - // forward - for (distance_type i = zero; i < n; ++i) - utf8::next(it, end); - } - } - - template - typename std::iterator_traits::difference_type distance( - octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::next(first, last); - return dist; - } - - template - octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) - { - utfchar32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) - { - if (start != end) - { - const utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); - if (utf8::internal::is_trail_surrogate(trail_surrogate)) - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - else - throw invalid_utf16(static_cast(trail_surrogate)); - } - else - throw invalid_utf16(static_cast(cp)); - } - // Lone trail surrogate - else if (utf8::internal::is_trail_surrogate(cp)) - throw invalid_utf16(static_cast(cp)); - - result = utf8::append(cp, result); - } - return result; - } - - template - u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start < end) - { - const utfchar32_t cp = utf8::next(start, end); - if (cp > 0xffff) - { // make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } - - template - octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::append(*(start++), result); - - return result; - } - - template - u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start < end) - (*result++) = utf8::next(start, end); - - return result; - } - - // The iterator class - template - class iterator - { - octet_iterator it; - octet_iterator range_start; - octet_iterator range_end; - - public: - typedef utfchar32_t value_type; - typedef utfchar32_t* pointer; - typedef utfchar32_t& reference; - typedef std::ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - iterator() {} - explicit iterator(const octet_iterator& octet_it, const octet_iterator& rangestart, - const octet_iterator& rangeend) - : it(octet_it), range_start(rangestart), range_end(rangeend) - { - if (it < range_start || it > range_end) - throw std::out_of_range("Invalid utf-8 iterator position"); - } - // the default "big three" are OK - octet_iterator base() const - { - return it; - } - utfchar32_t operator*() const - { - octet_iterator temp = it; - return utf8::next(temp, range_end); - } - bool operator==(const iterator& rhs) const - { - if (range_start != rhs.range_start || range_end != rhs.range_end) - throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); - return (it == rhs.it); - } - bool operator!=(const iterator& rhs) const - { - return !(operator==(rhs)); - } - iterator& operator++() - { - utf8::next(it, range_end); - return *this; - } - iterator operator++(int) - { - iterator temp = *this; - utf8::next(it, range_end); - return temp; - } - iterator& operator--() - { - utf8::prior(it, range_start); - return *this; - } - iterator operator--(int) - { - iterator temp = *this; - utf8::prior(it, range_start); - return temp; - } - }; // class iterator - -} // namespace utf8 - -#if UTF_CPP_CPLUSPLUS >= 202002L // C++ 20 or later - #include "cpp20.h" -#elif UTF_CPP_CPLUSPLUS >= 201703L // C++ 17 or later - #include "cpp17.h" -#elif UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later - #include "cpp11.h" -#endif // C++ 11 or later - -#endif // header guard + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + utfchar32_t cp; + public: + invalid_code_point(utfchar32_t codepoint) : cp(codepoint) {} + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid code point"; } + utfchar32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + utfchar8_t u8; + public: + invalid_utf8 (utfchar8_t u) : u8(u) {} + invalid_utf8 (char c) : u8(static_cast(c)) {} + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid UTF-8"; } + utfchar8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + utfchar16_t u16; + public: + invalid_utf16 (utfchar16_t u) : u16(u) {} + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid UTF-16"; } + utfchar16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(utfchar32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + return internal::append(cp, result); + } + + inline void append(utfchar32_t cp, std::string& s) + { + append(cp, std::back_inserter(s)); + } + + template + word_iterator append16(utfchar32_t cp, word_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + return internal::append16(cp, result); + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + out = utf8::append (replacement, out); + start = end; + break; + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const utfchar32_t replacement_marker = static_cast(utf8::internal::mask16(0xfffd)); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } + + inline std::string replace_invalid(const std::string& s) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + template + utfchar32_t next(octet_iterator& it, octet_iterator end) + { + utfchar32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(static_cast(*it)); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + utfchar32_t next16(word_iterator& it, word_iterator end) + { + utfchar32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next16(it, end, cp); + if (err_code == internal::NOT_ENOUGH_ROOM) + throw not_enough_room(); + return cp; + } + + template + utfchar32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + utfchar32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + const distance_type zero(0); + if (n < zero) { + // backward + for (distance_type i = n; i < zero; ++i) + utf8::prior(it, end); + } else { + // forward + for (distance_type i = zero; i < n; ++i) + utf8::next(it, end); + } + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + utfchar32_t cp = static_cast(utf8::internal::mask16(*start++)); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + const utfchar32_t trail_surrogate = static_cast(utf8::internal::mask16(*start++)); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + const utfchar32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + typedef utfchar32_t value_type; + typedef utfchar32_t* pointer; + typedef utfchar32_t& reference; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& rangestart, + const octet_iterator& rangeend) : + it(octet_it), range_start(rangestart), range_end(rangeend) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + utfchar32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#if UTF_CPP_CPLUSPLUS >= 202002L // C++ 20 or later +#include "cpp20.h" +#elif UTF_CPP_CPLUSPLUS >= 201703L // C++ 17 or later +#include "cpp17.h" +#elif UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later +#include "cpp11.h" +#endif // C++ 11 or later + +#endif //header guard + diff --git a/Include/Pipe/Extern/utf8/core.h b/Include/Pipe/Extern/utf8/core.h index 4494c538..8e128c18 100644 --- a/Include/Pipe/Extern/utf8/core.h +++ b/Include/Pipe/Extern/utf8/core.h @@ -43,9 +43,12 @@ DEALINGS IN THE SOFTWARE. #if UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later #define UTF_CPP_OVERRIDE override #define UTF_CPP_NOEXCEPT noexcept + #define UTF_CPP_STATIC_ASSERT(condition) static_assert(condition, "UTFCPP static assert"); #else // C++ 98/03 #define UTF_CPP_OVERRIDE #define UTF_CPP_NOEXCEPT throw() + // Not worth simulating static_assert: + #define UTF_CPP_STATIC_ASSERT(condition) (void)(condition); #endif // C++ 11 or later @@ -87,6 +90,7 @@ namespace internal { return static_cast(0xff & oc); } + template inline utfchar16_t mask16(u16_type oc) { @@ -101,17 +105,17 @@ namespace internal inline bool is_lead_surrogate(utfchar32_t cp) { - return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + return (cp >= static_cast(LEAD_SURROGATE_MIN) && cp <= static_cast(LEAD_SURROGATE_MAX)); } inline bool is_trail_surrogate(utfchar32_t cp) { - return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + return (cp >= static_cast(TRAIL_SURROGATE_MIN) && cp <= static_cast(TRAIL_SURROGATE_MAX)); } inline bool is_surrogate(utfchar32_t cp) { - return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + return (cp >= static_cast(LEAD_SURROGATE_MIN) && cp <= static_cast(TRAIL_SURROGATE_MAX)); } inline bool is_code_point_valid(utfchar32_t cp) @@ -143,15 +147,15 @@ namespace internal inline bool is_overlong_sequence(utfchar32_t cp, int length) { if (cp < 0x80) { - if (length != 1) + if (length != 1) return true; } else if (cp < 0x800) { - if (length != 2) + if (length != 2) return true; } else if (cp < 0x10000) { - if (length != 3) + if (length != 3) return true; } return false; @@ -181,7 +185,7 @@ namespace internal if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); return UTF8_OK; } @@ -189,10 +193,10 @@ namespace internal template utf_error get_sequence_2(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { - if (it == end) + if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -206,8 +210,8 @@ namespace internal { if (it == end) return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); + + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -215,7 +219,7 @@ namespace internal UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - code_point += (*it) & 0x3f; + code_point = static_cast(code_point + ((*it) & 0x3f)); return UTF8_OK; } @@ -226,7 +230,7 @@ namespace internal if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -234,11 +238,11 @@ namespace internal UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + code_point = static_cast(code_point + ((utf8::internal::mask8(*it) << 6) & 0xfff)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - code_point += (*it) & 0x3f; + code_point = static_cast(code_point + ((*it) & 0x3f)); return UTF8_OK; } @@ -290,7 +294,7 @@ namespace internal else err = OVERLONG_SEQUENCE; } - else + else err = INVALID_CODE_POINT; } @@ -308,6 +312,10 @@ namespace internal template utf_error validate_next16(word_iterator& it, word_iterator end, utfchar32_t& code_point) { + // Make sure the iterator dereferences a large enough type + typedef typename std::iterator_traits::value_type word_type; + UTF_CPP_STATIC_ASSERT(sizeof(word_type) >= sizeof(utfchar16_t)); + // Check the edge case: if (it == end) return NOT_ENOUGH_ROOM; // Save the original value of it so we can go back in case of failure @@ -326,14 +334,14 @@ namespace internal err = NOT_ENOUGH_ROOM; else if (is_lead_surrogate(first_word)) { const utfchar16_t second_word = *it++; - if (is_trail_surrogate(second_word)) { - code_point = (first_word << 10) + second_word + SURROGATE_OFFSET; + if (is_trail_surrogate(static_cast(second_word))) { + code_point = static_cast(first_word << 10) + static_cast(second_word) + SURROGATE_OFFSET; return UTF8_OK; - } else - err = INCOMPLETE_SEQUENCE; - + } else + err = INCOMPLETE_SEQUENCE; + } else { - err = INVALID_LEAD; + err = INVALID_LEAD; } } // error branch @@ -365,7 +373,7 @@ namespace internal } return result; } - + // One of the following overloads will be invoked from the API calls // A simple (but dangerous) case: the caller appends byte(s) to a char array @@ -395,6 +403,7 @@ namespace internal // the word_type. template word_iterator append16(utfchar32_t cp, word_iterator result) { + UTF_CPP_STATIC_ASSERT(sizeof(word_type) >= sizeof(utfchar16_t)); if (is_in_bmp(cp)) *(result++) = static_cast(cp); else { @@ -444,7 +453,7 @@ namespace internal inline const char* find_invalid(const char* str) { const char* end = str + std::strlen(str); - return find_invalid(str, end); + return find_invalid(str, end); } inline std::size_t find_invalid(const std::string& s) @@ -484,9 +493,8 @@ namespace internal inline bool starts_with_bom(const std::string& s) { return starts_with_bom(s.begin(), s.end()); - } + } } // namespace utf8 #endif // header guard - diff --git a/Include/Pipe/Extern/utf8/cpp11.h b/Include/Pipe/Extern/utf8/cpp11.h index 786a9be9..691633c8 100644 --- a/Include/Pipe/Extern/utf8/cpp11.h +++ b/Include/Pipe/Extern/utf8/cpp11.h @@ -28,42 +28,43 @@ DEALINGS IN THE SOFTWARE. #ifndef UTF8_FOR_CPP_a184c22c_d012_11e8_a8d5_f2801f1b9fd1 #define UTF8_FOR_CPP_a184c22c_d012_11e8_a8d5_f2801f1b9fd1 -#include "Pipe/Extern/utf8/checked.h" +#include "checked.h" namespace utf8 { - inline void append16(utfchar32_t cp, std::u16string& s) - { - append16(cp, std::back_inserter(s)); - } + inline void append16(utfchar32_t cp, std::u16string& s) + { + append16(cp, std::back_inserter(s)); + } - inline std::string utf16to8(const std::u16string& s) - { - std::string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::string utf16to8(const std::u16string& s) + { + std::string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - inline std::u16string utf8to16(const std::string& s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::u16string utf8to16(const std::string& s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - inline std::string utf32to8(const std::u32string& s) - { - std::string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::string utf32to8(const std::u32string& s) + { + std::string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - inline std::u32string utf8to32(const std::string& s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } -} // namespace utf8 + inline std::u32string utf8to32(const std::string& s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } +} // namespace utf8 + +#endif // header guard -#endif // header guard diff --git a/Include/Pipe/Extern/utf8/cpp17.h b/Include/Pipe/Extern/utf8/cpp17.h index 7131e232..07587300 100644 --- a/Include/Pipe/Extern/utf8/cpp17.h +++ b/Include/Pipe/Extern/utf8/cpp17.h @@ -28,69 +28,69 @@ DEALINGS IN THE SOFTWARE. #ifndef UTF8_FOR_CPP_7e906c01_03a3_4daf_b420_ea7ea952b3c9 #define UTF8_FOR_CPP_7e906c01_03a3_4daf_b420_ea7ea952b3c9 -#include "Pipe/Extern/utf8/cpp11.h" +#include "cpp11.h" namespace utf8 { - inline std::string utf16to8(std::u16string_view s) - { - std::string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u16string utf8to16(std::string_view s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::string utf32to8(std::u32string_view s) - { - std::string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u32string utf8to32(std::string_view s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::size_t find_invalid(std::string_view s) - { - std::string_view::const_iterator invalid = find_invalid(s.begin(), s.end()); - return (invalid == s.end()) ? std::string_view::npos - : static_cast(invalid - s.begin()); - } - - inline bool is_valid(std::string_view s) - { - return is_valid(s.begin(), s.end()); - } - - inline std::string replace_invalid(std::string_view s, char32_t replacement) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } - - inline std::string replace_invalid(std::string_view s) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline bool starts_with_bom(std::string_view s) - { - return starts_with_bom(s.begin(), s.end()); - } - -} // namespace utf8 - -#endif // header guard + inline std::string utf16to8(std::u16string_view s) + { + std::string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u16string utf8to16(std::string_view s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::string utf32to8(std::u32string_view s) + { + std::string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u32string utf8to32(std::string_view s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::size_t find_invalid(std::string_view s) + { + std::string_view::const_iterator invalid = find_invalid(s.begin(), s.end()); + return (invalid == s.end()) ? std::string_view::npos : static_cast(invalid - s.begin()); + } + + inline bool is_valid(std::string_view s) + { + return is_valid(s.begin(), s.end()); + } + + inline std::string replace_invalid(std::string_view s, char32_t replacement) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } + + inline std::string replace_invalid(std::string_view s) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline bool starts_with_bom(std::string_view s) + { + return starts_with_bom(s.begin(), s.end()); + } + +} // namespace utf8 + +#endif // header guard + diff --git a/Include/Pipe/Extern/utf8/cpp20.h b/Include/Pipe/Extern/utf8/cpp20.h index 6e16bad5..07b61d0f 100644 --- a/Include/Pipe/Extern/utf8/cpp20.h +++ b/Include/Pipe/Extern/utf8/cpp20.h @@ -28,97 +28,97 @@ DEALINGS IN THE SOFTWARE. #ifndef UTF8_FOR_CPP_207e906c01_03a3_4daf_b420_ea7ea952b3c9 #define UTF8_FOR_CPP_207e906c01_03a3_4daf_b420_ea7ea952b3c9 -#include "Pipe/Extern/utf8/cpp17.h" +#include "cpp17.h" namespace utf8 { - inline std::u8string utf16tou8(const std::u16string& s) - { - std::u8string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u8string utf16tou8(std::u16string_view s) - { - std::u8string result; - utf16to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u16string utf8to16(const std::u8string& s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u16string utf8to16(const std::u8string_view& s) - { - std::u16string result; - utf8to16(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u8string utf32tou8(const std::u32string& s) - { - std::u8string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u8string utf32tou8(const std::u32string_view& s) - { - std::u8string result; - utf32to8(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u32string utf8to32(const std::u8string& s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::u32string utf8to32(const std::u8string_view& s) - { - std::u32string result; - utf8to32(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline std::size_t find_invalid(const std::u8string& s) - { - std::u8string::const_iterator invalid = find_invalid(s.begin(), s.end()); - return (invalid == s.end()) ? std::string_view::npos - : static_cast(invalid - s.begin()); - } - - inline bool is_valid(const std::u8string& s) - { - return is_valid(s.begin(), s.end()); - } - - inline std::u8string replace_invalid(const std::u8string& s, char32_t replacement) - { - std::u8string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } - - inline std::u8string replace_invalid(const std::u8string& s) - { - std::u8string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } - - inline bool starts_with_bom(const std::u8string& s) - { - return starts_with_bom(s.begin(), s.end()); - } - -} // namespace utf8 - -#endif // header guard + inline std::u8string utf16tou8(const std::u16string& s) + { + std::u8string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u8string utf16tou8(std::u16string_view s) + { + std::u8string result; + utf16to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u16string utf8to16(const std::u8string& s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u16string utf8to16(const std::u8string_view& s) + { + std::u16string result; + utf8to16(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u8string utf32tou8(const std::u32string& s) + { + std::u8string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u8string utf32tou8(const std::u32string_view& s) + { + std::u8string result; + utf32to8(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u32string utf8to32(const std::u8string& s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::u32string utf8to32(const std::u8string_view& s) + { + std::u32string result; + utf8to32(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline std::size_t find_invalid(const std::u8string& s) + { + std::u8string::const_iterator invalid = find_invalid(s.begin(), s.end()); + return (invalid == s.end()) ? std::string_view::npos : static_cast(invalid - s.begin()); + } + + inline bool is_valid(const std::u8string& s) + { + return is_valid(s.begin(), s.end()); + } + + inline std::u8string replace_invalid(const std::u8string& s, char32_t replacement) + { + std::u8string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } + + inline std::u8string replace_invalid(const std::u8string& s) + { + std::u8string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } + + inline bool starts_with_bom(const std::u8string& s) + { + return starts_with_bom(s.begin(), s.end()); + } + +} // namespace utf8 + +#endif // header guard + diff --git a/Include/Pipe/Extern/utf8/unchecked.h b/Include/Pipe/Extern/utf8/unchecked.h index 7546d79c..173d0302 100644 --- a/Include/Pipe/Extern/utf8/unchecked.h +++ b/Include/Pipe/Extern/utf8/unchecked.h @@ -28,277 +28,259 @@ DEALINGS IN THE SOFTWARE. #ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#include "Pipe/Extern/utf8/core.h" +#include "core.h" namespace utf8 { - namespace unchecked - { - template - octet_iterator append(utfchar32_t cp, octet_iterator result) - { - return internal::append(cp, result); - } + namespace unchecked + { + template + octet_iterator append(utfchar32_t cp, octet_iterator result) + { + return internal::append(cp, result); + } - template - word_iterator append16(utfchar32_t cp, word_iterator result) - { - return internal::append16(cp, result); - } + template + word_iterator append16(utfchar32_t cp, word_iterator result) + { + return internal::append16(cp, result); + } - template - output_iterator replace_invalid( - octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) - { - while (start != end) - { - octet_iterator sequence_start = start; - internal::utf_error err_code = utf8::internal::validate_next(start, end); - switch (err_code) - { - case internal::UTF8_OK: - for (octet_iterator it = sequence_start; it != start; ++it) - *out++ = *it; - break; - case internal::NOT_ENOUGH_ROOM: - out = utf8::unchecked::append(replacement, out); - start = end; - break; - case internal::INVALID_LEAD: - out = utf8::unchecked::append(replacement, out); - ++start; - break; - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: - case internal::INVALID_CODE_POINT: - out = utf8::unchecked::append(replacement, out); - ++start; - // just one replacement mark for the sequence - while (start != end && utf8::internal::is_trail(*start)) - ++start; - break; - } - } - return out; - } + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + out = utf8::unchecked::append(replacement, out); + start = end; + break; + case internal::INVALID_LEAD: + out = utf8::unchecked::append(replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::unchecked::append(replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } - template - inline output_iterator replace_invalid( - octet_iterator start, octet_iterator end, output_iterator out) - { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); - return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); - } + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const utfchar32_t replacement_marker = static_cast(utf8::internal::mask16(0xfffd)); + return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); + } - inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); - return result; - } + inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); + return result; + } - inline std::string replace_invalid(const std::string& s) - { - std::string result; - replace_invalid(s.begin(), s.end(), std::back_inserter(result)); - return result; - } + inline std::string replace_invalid(const std::string& s) + { + std::string result; + replace_invalid(s.begin(), s.end(), std::back_inserter(result)); + return result; + } - template - utfchar32_t next(octet_iterator& it) - { - utfchar32_t cp = utf8::internal::mask8(*it); - switch (utf8::internal::sequence_length(it)) - { - case 1: break; - case 2: - it++; - cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); - break; - case 3: - ++it; - cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); - ++it; - cp += (*it) & 0x3f; - break; - case 4: - ++it; - cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); - ++it; - cp += (utf8::internal::mask8(*it) << 6) & 0xfff; - ++it; - cp += (*it) & 0x3f; - break; - } - ++it; - return cp; - } + template + utfchar32_t next(octet_iterator& it) + { + utfchar32_t cp = utf8::internal::mask8(*it); + switch (utf8::internal::sequence_length(it)) { + case 1: + break; + case 2: + ++it; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp = static_cast(cp + ((*it) & 0x3f)); + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp = static_cast(cp + ((utf8::internal::mask8(*it) << 6) & 0xfff)); + ++it; + cp = static_cast(cp + ((*it) & 0x3f)); + break; + } + ++it; + return cp; + } - template - utfchar32_t peek_next(octet_iterator it) - { - return utf8::unchecked::next(it); - } + template + utfchar32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } - template - utfchar32_t next16(word_iterator& it) - { - utfchar32_t cp = utf8::internal::mask16(*it++); - if (utf8::internal::is_lead_surrogate(cp)) - return (cp << 10) + *it++ + utf8::internal::SURROGATE_OFFSET; - return cp; - } + template + utfchar32_t next16(word_iterator& it) + { + utfchar32_t cp = utf8::internal::mask16(*it++); + if (utf8::internal::is_lead_surrogate(cp)) + return (cp << 10) + *it++ + utf8::internal::SURROGATE_OFFSET; + return cp; + } - template - utfchar32_t prior(octet_iterator& it) - { - while (utf8::internal::is_trail(*(--it))) - ; - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } + template + utfchar32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } - template - void advance(octet_iterator& it, distance_type n) - { - const distance_type zero(0); - if (n < zero) - { - // backward - for (distance_type i = n; i < zero; ++i) - utf8::unchecked::prior(it); - } - else - { - // forward - for (distance_type i = zero; i < n; ++i) - utf8::unchecked::next(it); - } - } + template + void advance(octet_iterator& it, distance_type n) + { + const distance_type zero(0); + if (n < zero) { + // backward + for (distance_type i = n; i < zero; ++i) + utf8::unchecked::prior(it); + } else { + // forward + for (distance_type i = zero; i < n; ++i) + utf8::unchecked::next(it); + } + } - template - typename std::iterator_traits::difference_type distance( - octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::unchecked::next(first); - return dist; - } + template + typename std::iterator_traits::difference_type + distance(octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } - template - octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) - { - utfchar32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) - { - if (start == end) - return result; - utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - } - result = utf8::unchecked::append(cp, result); - } - return result; - } + template + octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + utfchar32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start == end) + return result; + utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } - template - u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start < end) - { - utfchar32_t cp = utf8::unchecked::next(start); - if (cp > 0xffff) - { // make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = - static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } + template + u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + utfchar32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } - template - octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::unchecked::append(*(start++), result); + template + octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); - return result; - } + return result; + } - template - u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start < end) - (*result++) = utf8::unchecked::next(start); + template + u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); - return result; - } + return result; + } - // The iterator class - template - class iterator - { - octet_iterator it; + // The iterator class + template + class iterator { + octet_iterator it; + public: + typedef utfchar32_t value_type; + typedef utfchar32_t* pointer; + typedef utfchar32_t& reference; + typedef std::ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + iterator () {} + explicit iterator (const octet_iterator& octet_it): it(octet_it) {} + // the default "big three" are OK + octet_iterator base () const { return it; } + utfchar32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator - public: - typedef utfchar32_t value_type; - typedef utfchar32_t* pointer; - typedef utfchar32_t& reference; - typedef std::ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; - iterator() {} - explicit iterator(const octet_iterator& octet_it) : it(octet_it) {} - // the default "big three" are OK - octet_iterator base() const - { - return it; - } - utfchar32_t operator*() const - { - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } - bool operator==(const iterator& rhs) const - { - return (it == rhs.it); - } - bool operator!=(const iterator& rhs) const - { - return !(operator==(rhs)); - } - iterator& operator++() - { - ::std::advance(it, utf8::internal::sequence_length(it)); - return *this; - } - iterator operator++(int) - { - iterator temp = *this; - ::std::advance(it, utf8::internal::sequence_length(it)); - return temp; - } - iterator& operator--() - { - utf8::unchecked::prior(it); - return *this; - } - iterator operator--(int) - { - iterator temp = *this; - utf8::unchecked::prior(it); - return temp; - } - }; // class iterator + } // namespace utf8::unchecked +} // namespace utf8 - } // namespace unchecked -} // namespace utf8 +#endif // header guard - -#endif // header guard diff --git a/Include/PipeMemory.h b/Include/PipeMemory.h index 7dd3f0a2..f0c9a4e6 100644 --- a/Include/PipeMemory.h +++ b/Include/PipeMemory.h @@ -3,6 +3,7 @@ #pragma once #include "Pipe/Core/Limits.h" +#include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" #include "Pipe/Core/Utility.h" @@ -394,7 +395,7 @@ namespace p /** Arena defines the API used on all other arena types */ - class P_API Arena + class P_API Arena : public Castable { public: using AllocSignature = void*(Arena*, sizet size); @@ -508,6 +509,12 @@ namespace p { return nullptr; } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; class P_API ChildArena : public Arena @@ -522,6 +529,12 @@ namespace p { return *parent; } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; #pragma endregion Arena diff --git a/Include/PipeMemoryArenas.h b/Include/PipeMemoryArenas.h index c9fb3cba..3810ec6a 100644 --- a/Include/PipeMemoryArenas.h +++ b/Include/PipeMemoryArenas.h @@ -35,6 +35,12 @@ namespace p return false; } void Free(void* ptr, sizet size) {} + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; #pragma endregion Dummy Arena @@ -56,6 +62,12 @@ namespace p { return &stats; } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; #pragma endregion Heap Arena @@ -111,6 +123,12 @@ namespace p { return &stats; } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; // TMonoLinearArena works like a MonoLinearArena but providing a block on the stack as the block @@ -230,6 +248,12 @@ namespace p void Release(); void Grow(sizet size, sizet align = 0); + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; #pragma endregion Multi Linear @@ -320,6 +344,12 @@ namespace p void ReduceSlot( i32 slotIndex, Slot& slot, u8* const allocationStart, u8* const allocationEnd); void AbsorbFreeSpace(u8* const allocationStart, u8* const allocationEnd); + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; #pragma endregion Best Fit Arena @@ -414,6 +444,12 @@ namespace p void AbsorbFreeSpace(u32 allocationStart, u32 allocationEnd); static u32 ToOffset(void* data, void* block); + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; #pragma endregion Big Best Fit Arena } // namespace p diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index 2dd46c8a..61e3b51e 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -846,7 +846,7 @@ namespace p namespace p { #pragma region Objects - class P_API BaseObject : public Casteable + class P_API BaseObject : public Castable { protected: BaseObject() = default; @@ -953,7 +953,7 @@ namespace p /** * Cast a pointer into another type doing up-casting */ - template + template To* Cast(From* value) requires(Derived) { return value; @@ -962,7 +962,7 @@ namespace p /** * Cast a pointer into another type doing down-casting */ - template + template To* Cast(From* value) requires(Derived, From, false>) { static_assert(!IsPointer, diff --git a/Include/PipeSerialize.h b/Include/PipeSerialize.h index 78f92511..4cc081f4 100644 --- a/Include/PipeSerialize.h +++ b/Include/PipeSerialize.h @@ -41,7 +41,7 @@ namespace p #pragma region Reader - struct P_API Reader : public Casteable + struct P_API Reader : public Castable { friend IFormatReader; IFormatReader* formatReader = nullptr; @@ -199,7 +199,7 @@ namespace p #pragma region Writer - struct P_API Writer : public Casteable + struct P_API Writer : public Castable { friend IFormatWriter; IFormatWriter* formatWriter = nullptr; diff --git a/Src/Memory/MemoryStats.cpp b/Src/Memory/MemoryStats.cpp index 370ce0c2..a981c847 100644 --- a/Src/Memory/MemoryStats.cpp +++ b/Src/Memory/MemoryStats.cpp @@ -37,6 +37,12 @@ namespace p { std::free(ptr); } + + protected: + TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } }; // Getter to ensure readiness at static init time From 637c95e76703afd9b5d155af7fc34e887a9e77a7 Mon Sep 17 00:00:00 2001 From: muit Date: Thu, 5 Feb 2026 14:15:50 +0100 Subject: [PATCH 12/14] Remove Id children by default, small fixes --- Include/PipeECS.h | 16 ++++++---------- Src/PipeECS.cpp | 44 ++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/Include/PipeECS.h b/Include/PipeECS.h index c77aec47..b4436611 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -36,8 +36,8 @@ namespace p None = 0, // Remove entities instantly instead of waiting (deferred) Instant = 1 << 0, - // Remove children entities of the ones being removed - RemoveChildren = 1 << 1 + // Keep children entities of the ones being removed + KeepChildren = 1 << 1 }; P_DEFINE_FLAG_OPERATORS(RmIdFlags) @@ -228,7 +228,7 @@ namespace p private: TArray entities; TArray available; - TArray deferredRemoves; // List of ids that are invalid but not removed yet. + TArray deferredRemovals; // List of ids that are invalid but not removed yet. Arena* arena = nullptr; @@ -239,7 +239,7 @@ namespace p public: IdRegistry(Arena& arena = GetCurrentArena()) - : entities{arena}, available{arena}, deferredRemoves{arena}, arena{&arena} + : entities{arena}, available{arena}, deferredRemovals{arena}, arena{&arena} {} IdRegistry(IdRegistry&& other); IdRegistry(const IdRegistry& other); @@ -254,7 +254,7 @@ namespace p bool FlushDeferredRemovals(); const TArray& GetDeferredRemovals() const { - return deferredRemoves; + return deferredRemovals; } bool IsValid(Id id) const; bool WasRemoved(Id id) const; @@ -1292,7 +1292,6 @@ namespace p template Component& Get(const Id id) const { - P_Check(IsValid(id)); auto* const pool = GetPool(); P_Check(pool); return pool->Get(id); @@ -1300,7 +1299,6 @@ namespace p template TTuple Get(const Id id) const requires(sizeof...(Component) > 1) { - P_Check(IsValid(id)); return std::forward_as_tuple(Get(id)...); } template @@ -1312,14 +1310,12 @@ namespace p template TTuple TryGet(const Id id) const requires(sizeof...(Component) > 1) { - P_Check(IsValid(id)); return std::forward_as_tuple(TryGet(id)...); } template Component& GetOrAdd(Id id) { - P_Check(IsValid(id)); return AssurePool().GetOrAdd(id); } @@ -2298,7 +2294,7 @@ namespace p template void IdRegistry::Each(Callback cb) const { - if (available.IsEmpty()) + if (available.IsEmpty() && deferredRemovals.IsEmpty()) { for (i32 i = 0; i < entities.Size(); ++i) { diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index cf4852d8..1257d1c7 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -43,31 +43,31 @@ namespace p IdRegistry::IdRegistry(IdRegistry&& other) { std::unique_lock lock{other.mutex}; - entities = Move(other.entities); - available = Move(other.available); - deferredRemoves = Move(other.deferredRemoves); + entities = Move(other.entities); + available = Move(other.available); + deferredRemovals = Move(other.deferredRemovals); } IdRegistry::IdRegistry(const IdRegistry& other) { std::shared_lock lock{other.mutex}; - entities = other.entities; - available = other.available; - deferredRemoves = other.deferredRemoves; + entities = other.entities; + available = other.available; + deferredRemovals = other.deferredRemovals; } IdRegistry& IdRegistry::operator=(IdRegistry&& other) { std::unique_lock lock{other.mutex}; - entities = Move(other.entities); - available = Move(other.available); - deferredRemoves = Move(other.deferredRemoves); + entities = Move(other.entities); + available = Move(other.available); + deferredRemovals = Move(other.deferredRemovals); return *this; } IdRegistry& IdRegistry::operator=(const IdRegistry& other) { std::shared_lock lock{other.mutex}; - entities = other.entities; - available = other.available; - deferredRemoves = other.deferredRemoves; + entities = other.entities; + available = other.available; + deferredRemovals = other.deferredRemovals; return *this; } @@ -135,8 +135,8 @@ namespace p bool IdRegistry::DeferredRemove(TView ids) { std::unique_lock lock{mutex}; - deferredRemoves.ReserveMore(ids.Size()); - const u32 lastPending = deferredRemoves.Size(); + deferredRemovals.ReserveMore(ids.Size()); + const u32 lastPending = deferredRemovals.Size(); for (Id id : ids) { const Index index = id.GetIndex(); @@ -145,25 +145,25 @@ namespace p Id& storedId = entities[index]; if (id == storedId) { - deferredRemoves.Add(storedId); + deferredRemovals.Add(storedId); // Increase version to invalidate current entity storedId = MakeId(index, storedId.GetVersion() + 1u); } } } - return (deferredRemoves.Size() - lastPending) > 0; + return (deferredRemovals.Size() - lastPending) > 0; } bool IdRegistry::FlushDeferredRemovals() { - if (deferredRemoves.Size() > 0) + if (deferredRemovals.Size() > 0) { - available.ReserveMore(deferredRemoves.Size()); - for (Id id : deferredRemoves) + available.ReserveMore(deferredRemovals.Size()); + for (Id id : deferredRemovals) { available.Add(id.GetIndex()); } - deferredRemoves.Clear(); + deferredRemovals.Clear(); return true; } return false; @@ -196,7 +196,7 @@ namespace p u32 IdRegistry::Size() const { - return entities.Size() - available.Size() - deferredRemoves.Size(); + return entities.Size() - available.Size() - deferredRemovals.Size(); } @@ -1111,7 +1111,7 @@ namespace p bool RmId(EntityContext& ctx, TView ids, RmIdFlags flags) { TArray allIds; // Only used when removing children. Here for scope purposes. - if (HasFlag(flags, p::RmIdFlags::RemoveChildren)) + if (!HasFlag(flags, p::RmIdFlags::KeepChildren)) { allIds.Append(ids); GetAllIdChildren(ctx, ids, allIds); From ea9e7c2f9d867433399314b800abbf10b2d0dd76 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 6 Feb 2026 10:14:49 +0100 Subject: [PATCH 13/14] Entity Context copy fixes and tests --- Include/PipeECS.h | 12 +++++------- Src/PipeECS.cpp | 27 ++++++++++++++++++--------- Tests/ECS/Components.spec.cpp | 13 +++++++++++++ Tests/ECS/Filtering.spec.cpp | 18 ++++++++++++++++++ Tests/ECS/IdRegistry.spec.cpp | 20 ++++++++++---------- 5 files changed, 64 insertions(+), 26 deletions(-) diff --git a/Include/PipeECS.h b/Include/PipeECS.h index c77aec47..d69f0c68 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -249,8 +249,8 @@ namespace p Id Create(); void Create(TView newIds); + bool RemoveInstant(TView ids); bool Remove(TView ids); - bool DeferredRemove(TView ids); bool FlushDeferredRemovals(); const TArray& GetDeferredRemovals() const { @@ -1059,25 +1059,23 @@ namespace p template<> struct P_API TPool : public IPool { + friend EntityContext; using T = CRemoved; p::IdRegistry* idRegistry = nullptr; public: TPool(p::EntityContext& ctx, Arena& arena = GetCurrentArena()); - TPool(const TPool& other) : IPool(p::GetTypeId()), idRegistry{other.idRegistry} {} + TPool(const TPool& other) = delete; + TPool(TPool&& other) = delete; TPool& operator=(const TPool& other) { return *this; } - ~TPool() - { - Clear(); - } bool Has(Id id) const override { - return GetIdList().Contains(id); + return GetIdList().ContainsSorted(id); } i32 Size() const override; diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index cf4852d8..d8cb796d 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -110,7 +110,7 @@ namespace p entities.Append(newIds); } - bool IdRegistry::Remove(TView ids) + bool IdRegistry::RemoveInstant(TView ids) { std::unique_lock lock{mutex}; available.ReserveMore(ids.Size()); @@ -132,7 +132,7 @@ namespace p return (available.Size() - lastAvailable) > 0; } - bool IdRegistry::DeferredRemove(TView ids) + bool IdRegistry::Remove(TView ids) { std::unique_lock lock{mutex}; deferredRemoves.ReserveMore(ids.Size()); @@ -145,7 +145,7 @@ namespace p Id& storedId = entities[index]; if (id == storedId) { - deferredRemoves.Add(storedId); + deferredRemoves.AddSorted(storedId); // Increase version to invalidate current entity storedId = MakeId(index, storedId.GetVersion() + 1u); } @@ -564,7 +564,7 @@ namespace p void TPool::Add(Id id, CRemoved) { - idRegistry->DeferredRemove(id); + idRegistry->Remove(id); } const TArray& TPool::GetIdList() const @@ -640,7 +640,10 @@ namespace p } - EntityContext::EntityContext() {} + EntityContext::EntityContext() + { + AssurePool(); + } EntityContext::EntityContext(const EntityContext& other) noexcept { @@ -706,10 +709,14 @@ namespace p // Copy component pools. Assume already sorted for (const PoolInstance& otherInstance : other.pools) { - PoolInstance instance{otherInstance}; - pools.Add(Move(instance)); + if (otherInstance.pool->Size() > 0) + { + pools.Add(PoolInstance{otherInstance}); + } } + AssurePool().idRegistry = &idRegistry; + // TODO: Copy statics // TODO: Cache pools } @@ -720,6 +727,8 @@ namespace p pools = Move(other.pools); statics = Move(other.statics); + AssurePool().idRegistry = &idRegistry; + // TODO: Move statics // TODO: Cache pools } @@ -1125,11 +1134,11 @@ namespace p { pool.GetPool()->Remove(ids); } - return ctx.GetIdRegistry().Remove(ids); + return ctx.GetIdRegistry().RemoveInstant(ids); } else { - return ctx.GetIdRegistry().DeferredRemove(ids); + return ctx.GetIdRegistry().Remove(ids); } } diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 104d13b9..24e57cde 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -190,6 +190,7 @@ go_bandit([]() { AssertThat(ctxb.TryGet(id), !Equals(nullptr)); // Holds component values + AssertThat(ctxb.Has(id2), Is().True()); AssertThat(ctxb.Get(id2).a, Equals(2)); }); @@ -276,5 +277,17 @@ go_bandit([]() { AssertThat(ctx.Has(id), Is().True()); AssertThat(ctx.TryGet(id), !Equals(nullptr)); }); + + it("Can access CRemoved", [&]() { + EntityContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.Has(id), Is().True()); + AssertThat(ctx.TryGet(id), !Equals(nullptr)); + }); }); }); diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index 5b402e5c..28c9c982 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -204,5 +204,23 @@ go_bandit([]() { ExcludeIdsWithout(ctx, ids4); AssertThat(ids4.Contains(id1), Is().False()); }); + + it("Can filter CRemoved", [&]() { + RmId(ctx, id1); + RmId(ctx, id2); + RmId(ctx, id3); + + TArray ids1 = FindAllIdsWith(ctx); + AssertThat(ids1.Contains(id1), Is().True()); + TArray ids2 = FindAllIdsWith(ctx); + AssertThat(ids2.Contains(id1), Is().True()); + AssertThat(ids2.Contains(id2), Is().True()); + AssertThat(ids2.Contains(id3), Is().True()); + AssertThat(ids2.Size(), Equals(3)); + + TArray ids3 = FindAllIdsWith(ctx); + AssertThat(ids3.Contains(id1), Is().True()); + AssertThat(ids3.Contains(id2), Is().True()); + }); }); }); diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 1ccecb9a..b6a362bc 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -39,7 +39,7 @@ go_bandit([]() { IdRegistry ids; Id id = ids.Create(); AssertThat(ids.Size(), Equals(1)); - AssertThat(ids.Remove(id), Is().True()); + AssertThat(ids.RemoveInstant(id), Is().True()); AssertThat(ids.IsValid(id), Is().False()); AssertThat(ids.Size(), Equals(0)); }); @@ -48,7 +48,7 @@ go_bandit([]() { IdRegistry ids; Id id1 = ids.Create(); ids.Create(); - AssertThat(ids.Remove(id1), Is().True()); + AssertThat(ids.RemoveInstant(id1), Is().True()); AssertThat(ids.IsValid(id1), Is().False()); AssertThat(ids.Size(), Equals(1)); }); @@ -57,7 +57,7 @@ go_bandit([]() { IdRegistry ids; ids.Create(); Id id2 = ids.Create(); - AssertThat(ids.Remove(id2), Is().True()); + AssertThat(ids.RemoveInstant(id2), Is().True()); AssertThat(ids.IsValid(id2), Is().False()); AssertThat(ids.Size(), Equals(1)); }); @@ -66,7 +66,7 @@ go_bandit([]() { IdRegistry ids; Id id = ids.Create(); AssertThat(ids.Size(), Equals(1)); - AssertThat(ids.DeferredRemove(id), Is().True()); + AssertThat(ids.Remove(id), Is().True()); AssertThat(ids.IsValid(id), Is().False()); AssertThat(ids.Size(), Equals(0)); }); @@ -75,7 +75,7 @@ go_bandit([]() { IdRegistry ids; Id id1 = ids.Create(); ids.Create(); - AssertThat(ids.DeferredRemove(id1), Is().True()); + AssertThat(ids.Remove(id1), Is().True()); AssertThat(ids.IsValid(id1), Is().False()); AssertThat(ids.Size(), Equals(1)); }); @@ -84,7 +84,7 @@ go_bandit([]() { IdRegistry ids; ids.Create(); Id id2 = ids.Create(); - AssertThat(ids.DeferredRemove(id2), Is().True()); + AssertThat(ids.Remove(id2), Is().True()); AssertThat(ids.IsValid(id2), Is().False()); AssertThat(ids.Size(), Equals(1)); }); @@ -94,7 +94,7 @@ go_bandit([]() { ids.Create(); Id id = ids.Create(); ids.Create(); - AssertThat(ids.Remove(id), Is().True()); + AssertThat(ids.RemoveInstant(id), Is().True()); Id id2 = ids.Create(); AssertThat(id2.GetIndex(), Equals(id.GetIndex())); Id id3 = ids.Create(); @@ -106,7 +106,7 @@ go_bandit([]() { ids.Create(); Id id = ids.Create(); ids.Create(); - AssertThat(ids.DeferredRemove(id), Is().True()); + AssertThat(ids.Remove(id), Is().True()); Id id2 = ids.Create(); AssertThat(id2.GetIndex(), !Equals(id.GetIndex())); ids.FlushDeferredRemovals(); @@ -137,7 +137,7 @@ go_bandit([]() { ids.Create(list); AssertThat(ids.Size(), Equals(3)); - AssertThat(ids.Remove(list), Is().True()); + AssertThat(ids.RemoveInstant(list), Is().True()); AssertThat(ids.Size(), Equals(0)); for (i32 i = 0; i < list.Size(); ++i) @@ -152,7 +152,7 @@ go_bandit([]() { ids.Create(list); AssertThat(ids.Size(), Equals(3)); - AssertThat(ids.DeferredRemove(list), Is().True()); + AssertThat(ids.Remove(list), Is().True()); AssertThat(ids.Size(), Equals(0)); for (i32 i = 0; i < list.Size(); ++i) From 0617ac80597c1f5bc250bf49ccc34d78dd4a1927 Mon Sep 17 00:00:00 2001 From: muit Date: Wed, 11 Feb 2026 22:27:18 +0100 Subject: [PATCH 14/14] Small changes to ECS filtering --- Include/PipeECS.h | 26 ++++----------- Src/PipeECS.cpp | 83 +++++++++++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/Include/PipeECS.h b/Include/PipeECS.h index 0f48010e..d2f40d59 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -1860,6 +1860,8 @@ namespace p /** Find all ids containing any of the components. Prevents duplicates */ P_API void FindAllIdsWithAnyUnique(TView pools, TArray& ids); + P_API Id GetFirstIdWith(TView pools); + // Templated API @@ -2086,7 +2088,7 @@ namespace p template void FindAllIdsWith(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) { - FindAllIdsWith({&access.template AssurePool()...}, ids); + FindAllIdsWith({access.template GetPool()...}, ids); } @@ -2116,7 +2118,7 @@ namespace p template void FindAllIdsWithAny(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) { - FindAllIdsWithAny({&access.template AssurePool()...}, ids); + FindAllIdsWithAny({access.template GetPool()...}, ids); } /** @@ -2131,7 +2133,7 @@ namespace p void FindAllIdsWithAnyUnique(const AccessType& access, TArray& ids) requires(sizeof...(C) >= 1) { - FindAllIdsWithAnyUnique({&access.template AssurePool()...}, ids); + FindAllIdsWithAnyUnique({access.template GetPool()...}, ids); } /** @@ -2167,23 +2169,9 @@ namespace p } template - Id GetFirstId(const AccessType& access) + Id GetFirstIdWith(const AccessType& access) { - if constexpr (sizeof...(C) == 1) // Only one component? - { - const IPool* pool = access.template GetPool(); - if (pool && pool->Size() > 0) - { - return *pool->begin(); - } - return NoId; - } - else - { - TArray ids; - FindAllIdsWith(access, ids); - return ids.IsEmpty() ? NoId : ids[0]; - } + return GetFirstIdWith({access.template GetPool()...}); } #pragma endregion Filtering diff --git a/Src/PipeECS.cpp b/Src/PipeECS.cpp index e60410e4..8a4f9da0 100644 --- a/Src/PipeECS.cpp +++ b/Src/PipeECS.cpp @@ -578,10 +578,14 @@ namespace p i32 minIndex = NO_INDEX; for (i32 i = 0; i < pools.Size(); ++i) { - const i32 size = pools[i]->Size(); - if (size < minSize) + auto* pool = pools[i]; + if (!pool || pool->Size() <= 0) // Empty/inexistent pools are the smallest possible { - minSize = size; + return i; + } + else if (pool->Size() < minSize) + { + minSize = pool->Size(); minIndex = i; } } @@ -975,26 +979,21 @@ namespace p void FindAllIdsWith(TView pools, TArray& ids) { + ids.Clear(false); if (pools.IsEmpty()) { return; } + const i32 smallestIdx = GetSmallestPool(pools); + const IPool* iterablePool = pools[smallestIdx]; - for (const IPool* pool : pools) + // An empty iterable pool means at least one of the required components will never be there, + // so we return nothing + if (!iterablePool || iterablePool->Size() == 0) { - if (!P_EnsureMsg(pool, - "One of the pools is null. Is the access missing one or more of the " - "specified " - "components?")) - { - return; - } + return; } - const i32 smallestIdx = GetSmallestPool(pools); - const IPool* iterablePool = pools[smallestIdx]; - - ids.Clear(false); ids.Reserve(iterablePool->Size()); for (Id id : *iterablePool) { @@ -1018,6 +1017,7 @@ namespace p void FindAllIdsWithAny(TView pools, TArray& ids) { + ids.Clear(false); if (pools.IsEmpty()) { return; @@ -1025,24 +1025,16 @@ namespace p for (const IPool* pool : pools) { - if (!P_EnsureMsg(pool, - "One of the pools is null. Is the access missing one or more of the " - "specified " - "components?")) + if (pool) { - return; + ids.Append(pool->begin(), pool->end()); } } - - ids.Clear(); - for (const IPool* pool : pools) - { - ids.Append(pool->begin(), pool->end()); - } } void FindAllIdsWithAnyUnique(TView pools, TArray& ids) { + ids.Clear(false); if (pools.IsEmpty()) { return; @@ -1051,31 +1043,50 @@ namespace p i32 maxPossibleSize = 0; for (const IPool* pool : pools) { - if (!P_EnsureMsg(pool, - "One of the pools is null. Is the access missing one or more of the " - "specified " - "components?")) + if (pool) { - return; + maxPossibleSize += pool->Size(); } - - maxPossibleSize += pool->Size(); } TSet idsSet; idsSet.Reserve(maxPossibleSize); for (const IPool* pool : pools) { - for (Id id : *pool) + if (pool) { - idsSet.Insert(id); + for (Id id : *pool) + { + idsSet.Insert(id); + } } } - ids.Clear(); ids.Append(idsSet.begin(), idsSet.end()); } + Id GetFirstIdWith(TView pools) + { + if (pools.Size() == 1) + { + const auto* pool = pools[0]; + if (pool && pool->Size() > 0) + { + return *pool->begin(); + } + } + else + { + TArray ids; + FindAllIdsWith(pools, ids); + if (!ids.IsEmpty()) + { + return ids[0]; + } + } + return NoId; + } + void Read(Reader& ct, CParent& val) { ct.Serialize(val.children);